Ver código fonte

Feat/op compose (#1392)

Nathan.fooo 2 anos atrás
pai
commit
783fd40f63

+ 2 - 4
frontend/rust-lib/flowy-document/src/editor/document.rs

@@ -2,9 +2,7 @@ use bytes::Bytes;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_revision::{RevisionCompress, RevisionObjectDeserializer, RevisionObjectSerializer};
 use flowy_sync::entities::revision::Revision;
-use lib_ot::core::{
-    Body, Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction,
-};
+use lib_ot::core::{Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction};
 use lib_ot::text_delta::DeltaTextOperationBuilder;
 
 #[derive(Debug)]
@@ -46,7 +44,7 @@ pub(crate) fn make_tree_context() -> NodeTreeContext {
 
 pub fn initial_document_content() -> String {
     let delta = DeltaTextOperationBuilder::new().insert("").build();
-    let node_data = NodeDataBuilder::new("text").insert_body(Body::Delta(delta)).build();
+    let node_data = NodeDataBuilder::new("text").insert_delta(delta).build();
     let editor_node = NodeDataBuilder::new("editor").add_node_data(node_data).build();
     let node_operation = NodeOperation::Insert {
         path: vec![0].into(),

+ 23 - 16
frontend/rust-lib/flowy-document/src/editor/document_serde.rs

@@ -117,7 +117,8 @@ impl DocumentTransaction {
 
 impl std::convert::From<Transaction> for DocumentTransaction {
     fn from(transaction: Transaction) -> Self {
-        let (before_selection, after_selection) = match transaction.extension {
+        let (operations, extension) = transaction.split();
+        let (before_selection, after_selection) = match extension {
             Extension::Empty => (Selection::default(), Selection::default()),
             Extension::TextSelection {
                 before_selection,
@@ -126,9 +127,7 @@ impl std::convert::From<Transaction> for DocumentTransaction {
         };
 
         DocumentTransaction {
-            operations: transaction
-                .operations
-                .into_inner()
+            operations: operations
                 .into_iter()
                 .map(|operation| operation.as_ref().into())
                 .collect(),
@@ -139,19 +138,16 @@ impl std::convert::From<Transaction> for DocumentTransaction {
 }
 
 impl std::convert::From<DocumentTransaction> for Transaction {
-    fn from(transaction: DocumentTransaction) -> Self {
-        Transaction {
-            operations: transaction
-                .operations
-                .into_iter()
-                .map(|operation| operation.into())
-                .collect::<Vec<NodeOperation>>()
-                .into(),
-            extension: Extension::TextSelection {
-                before_selection: transaction.before_selection,
-                after_selection: transaction.after_selection,
-            },
+    fn from(document_transaction: DocumentTransaction) -> Self {
+        let mut transaction = Transaction::new();
+        for document_operation in document_transaction.operations {
+            transaction.push_operation(document_operation);
         }
+        transaction.extension = Extension::TextSelection {
+            before_selection: document_transaction.before_selection,
+            after_selection: document_transaction.after_selection,
+        };
+        transaction
     }
 }
 
@@ -374,6 +370,17 @@ mod tests {
         let _ = serde_json::to_string_pretty(&document).unwrap();
     }
 
+    // #[test]
+    // fn document_operation_compose_test() {
+    //     let json = include_str!("./test.json");
+    //     let transaction: Transaction = Transaction::from_json(json).unwrap();
+    //     let json = transaction.to_json().unwrap();
+    //     // let transaction: Transaction = Transaction::from_json(&json).unwrap();
+    //     let document = Document::from_transaction(transaction).unwrap();
+    //     let content = document.get_content(false).unwrap();
+    //     println!("{}", json);
+    // }
+
     const EXAMPLE_DOCUMENT: &str = r#"{
   "document": {
     "type": "editor",

+ 1 - 1
frontend/rust-lib/flowy-document/tests/editor/serde_test.rs

@@ -89,7 +89,7 @@ fn delta_deserialize_null_test() {
     let delta1 = DeltaTextOperations::from_json(json).unwrap();
 
     let mut attribute = BuildInTextAttribute::Bold(true);
-    attribute.remove_value();
+    attribute.clear();
 
     let delta2 = DeltaOperationBuilder::new()
         .retain_with_attributes(7, attribute.into())

+ 2 - 2
frontend/rust-lib/flowy-document/tests/new_document/script.rs

@@ -3,7 +3,7 @@ use flowy_document::editor::{AppFlowyDocumentEditor, Document, DocumentTransacti
 use flowy_document::entities::DocumentVersionPB;
 use flowy_test::helper::ViewTest;
 use flowy_test::FlowySDKTest;
-use lib_ot::core::{Body, Changeset, NodeDataBuilder, NodeOperation, Path, Transaction};
+use lib_ot::core::{Changeset, NodeDataBuilder, NodeOperation, Path, Transaction};
 use lib_ot::text_delta::DeltaTextOperations;
 use std::sync::Arc;
 
@@ -64,7 +64,7 @@ impl DocumentEditorTest {
     async fn run_script(&self, script: EditScript) {
         match script {
             EditScript::InsertText { path, delta } => {
-                let node_data = NodeDataBuilder::new("text").insert_body(Body::Delta(delta)).build();
+                let node_data = NodeDataBuilder::new("text").insert_delta(delta).build();
                 let operation = NodeOperation::Insert {
                     path,
                     nodes: vec![node_data],

+ 0 - 1
frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs

@@ -119,7 +119,6 @@ fn read_workspaces_on_server(
         let workspace_revs = server.read_workspace(&token, params).await?;
         let _ = persistence
             .begin_transaction(|transaction| {
-                tracing::trace!("Save {} workspace", workspace_revs.len());
                 for workspace_rev in &workspace_revs {
                     let m_workspace = workspace_rev.clone();
                     let app_revs = m_workspace.apps.clone();

+ 8 - 1
shared-lib/lib-ot/src/core/attributes/attribute.rs

@@ -12,7 +12,14 @@ pub struct AttributeEntry {
 }
 
 impl AttributeEntry {
-    pub fn remove_value(&mut self) {
+    pub fn new<K: Into<AttributeKey>, V: Into<AttributeValue>>(key: K, value: V) -> Self {
+        Self {
+            key: key.into(),
+            value: value.into(),
+        }
+    }
+
+    pub fn clear(&mut self) {
         self.value.ty = None;
         self.value.value = None;
     }

+ 1 - 0
shared-lib/lib-ot/src/core/node_tree/mod.rs

@@ -3,6 +3,7 @@
 mod node;
 mod node_serde;
 mod operation;
+mod operation_serde;
 mod path;
 mod transaction;
 mod transaction_serde;

+ 57 - 6
shared-lib/lib-ot/src/core/node_tree/node.rs

@@ -1,7 +1,7 @@
 use super::node_serde::*;
 use crate::core::attributes::{AttributeHashMap, AttributeKey, AttributeValue};
 use crate::core::Body::Delta;
-use crate::core::OperationTransform;
+use crate::core::{AttributeEntry, OperationTransform};
 use crate::errors::OTError;
 use crate::text_delta::DeltaTextOperations;
 use serde::{Deserialize, Serialize};
@@ -69,14 +69,18 @@ impl NodeDataBuilder {
     /// Inserts attributes to the builder's node.
     ///
     /// The attributes will be replace if they shared the same key
-    pub fn insert_attribute(mut self, key: AttributeKey, value: AttributeValue) -> Self {
-        self.node.attributes.insert(key, value);
+    pub fn insert_attribute<K: Into<AttributeKey>, V: Into<AttributeValue>>(mut self, key: K, value: V) -> Self {
+        self.node.attributes.insert(key.into(), value);
         self
     }
 
-    /// Inserts a body to the builder's node
-    pub fn insert_body(mut self, body: Body) -> Self {
-        self.node.body = body;
+    pub fn insert_attribute_entry(mut self, entry: AttributeEntry) -> Self {
+        self.node.attributes.insert_entry(entry);
+        self
+    }
+
+    pub fn insert_delta(mut self, delta: DeltaTextOperations) -> Self {
+        self.node.body = Body::Delta(delta);
         self
     }
 
@@ -174,6 +178,18 @@ pub enum Changeset {
 }
 
 impl Changeset {
+    pub fn is_delta(&self) -> bool {
+        match self {
+            Changeset::Delta { .. } => true,
+            Changeset::Attributes { .. } => false,
+        }
+    }
+    pub fn is_attribute(&self) -> bool {
+        match self {
+            Changeset::Delta { .. } => false,
+            Changeset::Attributes { .. } => true,
+        }
+    }
     pub fn inverted(&self) -> Changeset {
         match self {
             Changeset::Delta { delta, inverted } => Changeset::Delta {
@@ -186,6 +202,41 @@ impl Changeset {
             },
         }
     }
+
+    pub fn compose(&mut self, other: &Changeset) -> Result<(), OTError> {
+        match (self, other) {
+            (
+                Changeset::Delta { delta, inverted },
+                Changeset::Delta {
+                    delta: other_delta,
+                    inverted: _,
+                },
+            ) => {
+                let original = delta.invert(inverted);
+                let new_delta = delta.compose(other_delta)?;
+                let new_inverted = new_delta.invert(&original);
+
+                *delta = new_delta;
+                *inverted = new_inverted;
+                Ok(())
+            }
+            (
+                Changeset::Attributes { new, old },
+                Changeset::Attributes {
+                    new: other_new,
+                    old: other_old,
+                },
+            ) => {
+                *new = other_new.clone();
+                *old = other_old.clone();
+                Ok(())
+            }
+            (left, right) => {
+                let err = format!("Compose changeset failed. {:?} can't compose {:?}", left, right);
+                Err(OTError::compose().context(err))
+            }
+        }
+    }
 }
 
 /// [`Node`] represents as a leaf in the [`NodeTree`].

+ 147 - 36
shared-lib/lib-ot/src/core/node_tree/operation.rs

@@ -1,5 +1,6 @@
-use crate::core::{Changeset, NodeData, Path};
+use crate::core::{Body, Changeset, NodeData, OperationTransform, Path};
 use crate::errors::OTError;
+
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 
@@ -33,7 +34,80 @@ impl NodeOperation {
         }
     }
 
-    pub fn invert(&self) -> NodeOperation {
+    pub fn is_update_delta(&self) -> bool {
+        match self {
+            NodeOperation::Insert { .. } => false,
+            NodeOperation::Update { path: _, changeset } => changeset.is_delta(),
+            NodeOperation::Delete { .. } => false,
+        }
+    }
+
+    pub fn is_update_attribute(&self) -> bool {
+        match self {
+            NodeOperation::Insert { .. } => false,
+            NodeOperation::Update { path: _, changeset } => changeset.is_attribute(),
+            NodeOperation::Delete { .. } => false,
+        }
+    }
+    pub fn is_insert(&self) -> bool {
+        match self {
+            NodeOperation::Insert { .. } => true,
+            NodeOperation::Update { .. } => false,
+            NodeOperation::Delete { .. } => false,
+        }
+    }
+    pub fn can_compose(&self, other: &NodeOperation) -> bool {
+        if self.get_path() != other.get_path() {
+            return false;
+        }
+        if self.is_update_delta() && other.is_update_delta() {
+            return true;
+        }
+
+        if self.is_update_attribute() && other.is_update_attribute() {
+            return true;
+        }
+
+        if self.is_insert() && other.is_update_delta() {
+            return true;
+        }
+        false
+    }
+
+    pub fn compose(&mut self, other: &NodeOperation) -> Result<(), OTError> {
+        match (self, other) {
+            (
+                NodeOperation::Insert { path: _, nodes },
+                NodeOperation::Update {
+                    path: _other_path,
+                    changeset,
+                },
+            ) => {
+                match changeset {
+                    Changeset::Delta { delta, inverted: _ } => {
+                        if let Body::Delta(old_delta) = &mut nodes.last_mut().unwrap().body {
+                            let new_delta = old_delta.compose(delta)?;
+                            *old_delta = new_delta;
+                        }
+                    }
+                    Changeset::Attributes { new: _, old: _ } => {
+                        return Err(OTError::compose().context("Can't compose the attributes changeset"));
+                    }
+                }
+                Ok(())
+            }
+            (
+                NodeOperation::Update { path: _, changeset },
+                NodeOperation::Update {
+                    path: _,
+                    changeset: other_changeset,
+                },
+            ) => changeset.compose(other_changeset),
+            (_left, _right) => Err(OTError::compose().context("Can't compose the operation")),
+        }
+    }
+
+    pub fn inverted(&self) -> NodeOperation {
         match self {
             NodeOperation::Insert { path, nodes } => NodeOperation::Delete {
                 path: path.clone(),
@@ -99,61 +173,98 @@ impl NodeOperation {
     }
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+type OperationIndexMap = Vec<Arc<NodeOperation>>;
+
+#[derive(Debug, Clone, Default)]
 pub struct NodeOperations {
-    operations: Vec<Arc<NodeOperation>>,
+    inner: OperationIndexMap,
 }
 
 impl NodeOperations {
-    pub fn into_inner(self) -> Vec<Arc<NodeOperation>> {
-        self.operations
+    pub fn new() -> Self {
+        Self::default()
     }
 
-    pub fn push_op(&mut self, operation: NodeOperation) {
-        self.operations.push(Arc::new(operation));
+    pub fn from_operations(operations: Vec<NodeOperation>) -> Self {
+        let mut ops = Self::new();
+        for op in operations {
+            ops.push_op(op)
+        }
+        ops
     }
 
-    pub fn extend(&mut self, other: NodeOperations) {
-        for operation in other.operations {
-            self.operations.push(operation);
-        }
+    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)
     }
-}
 
-impl std::ops::Deref for NodeOperations {
-    type Target = Vec<Arc<NodeOperation>>;
+    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)
+    }
 
-    fn deref(&self) -> &Self::Target {
-        &self.operations
+    pub fn values(&self) -> &Vec<Arc<NodeOperation>> {
+        &self.inner
     }
-}
 
-impl std::ops::DerefMut for NodeOperations {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.operations
+    pub fn values_mut(&mut self) -> &mut Vec<Arc<NodeOperation>> {
+        &mut self.inner
     }
-}
 
-impl std::convert::From<Vec<NodeOperation>> for NodeOperations {
-    fn from(operations: Vec<NodeOperation>) -> Self {
-        Self::new(operations)
+    pub fn len(&self) -> usize {
+        self.values().len()
     }
-}
 
-impl NodeOperations {
-    pub fn new(operations: Vec<NodeOperation>) -> Self {
-        Self {
-            operations: operations.into_iter().map(Arc::new).collect(),
+    pub fn is_empty(&self) -> bool {
+        self.inner.is_empty()
+    }
+
+    pub fn into_inner(self) -> Vec<Arc<NodeOperation>> {
+        self.inner
+    }
+
+    pub fn push_op<T: Into<Arc<NodeOperation>>>(&mut self, other: T) {
+        let other = other.into();
+        if let Some(last_operation) = self.inner.last_mut() {
+            if last_operation.can_compose(&other) {
+                let mut_operation = Arc::make_mut(last_operation);
+                if mut_operation.compose(&other).is_ok() {
+                    return;
+                }
+            }
         }
+
+        // if let Some(operations) = self.inner.get_mut(other.get_path()) {
+        //     if let Some(last_operation) = operations.last_mut() {
+        //         if last_operation.can_compose(&other) {
+        //             let mut_operation = Arc::make_mut(last_operation);
+        //             if mut_operation.compose(&other).is_ok() {
+        //                 return;
+        //             }
+        //         }
+        //     }
+        // }
+        // If the passed-in operation can't be composed, then append it to the end.
+        self.inner.push(other);
     }
 
-    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)
+    pub fn compose(&mut self, other: NodeOperations) {
+        for operation in other.values() {
+            self.push_op(operation.clone());
+        }
     }
 
-    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)
+    pub fn inverted(&self) -> Self {
+        let mut operations = Self::new();
+        for operation in self.values() {
+            operations.push_op(operation.inverted());
+        }
+        operations
+    }
+}
+
+impl std::convert::From<Vec<NodeOperation>> for NodeOperations {
+    fn from(operations: Vec<NodeOperation>) -> Self {
+        Self::from_operations(operations)
     }
 }

+ 34 - 181
shared-lib/lib-ot/src/core/node_tree/operation_serde.rs

@@ -1,196 +1,49 @@
-use crate::core::{AttributeHashMap, Changeset, Path};
-use crate::text_delta::TextOperations;
-use serde::de::{self, MapAccess, Visitor};
-use serde::ser::SerializeMap;
-use serde::{Deserializer, Serializer};
-use std::convert::TryInto;
+use crate::core::{NodeOperation, NodeOperations};
+use serde::de::{SeqAccess, Visitor};
+use serde::ser::SerializeSeq;
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use std::fmt;
-use std::marker::PhantomData;
 
-#[allow(dead_code)]
-pub fn serialize_changeset<S>(path: &Path, changeset: &Changeset, serializer: S) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-{
-    let mut map = serializer.serialize_map(Some(3))?;
-    map.serialize_key("path")?;
-    map.serialize_value(path)?;
-
-    match changeset {
-        Changeset::Delta { delta, inverted } => {
-            map.serialize_key("delta")?;
-            map.serialize_value(delta)?;
-            map.serialize_key("inverted")?;
-            map.serialize_value(inverted)?;
-            map.end()
-        }
-        Changeset::Attributes { new, old } => {
-            map.serialize_key("new")?;
-            map.serialize_value(new)?;
-            map.serialize_key("old")?;
-            map.serialize_value(old)?;
-            map.end()
+impl Serialize for NodeOperations {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let operations = self.values();
+        let mut seq = serializer.serialize_seq(Some(operations.len()))?;
+        for operation in operations {
+            let _ = seq.serialize_element(&operation)?;
         }
+        seq.end()
     }
 }
 
-#[allow(dead_code)]
-pub fn deserialize_changeset<'de, D>(deserializer: D) -> Result<(Path, Changeset), D::Error>
-where
-    D: Deserializer<'de>,
-{
-    struct ChangesetVisitor();
+impl<'de> Deserialize<'de> for NodeOperations {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct NodeOperationsVisitor();
 
-    impl<'de> Visitor<'de> for ChangesetVisitor {
-        type Value = (Path, Changeset);
+        impl<'de> Visitor<'de> for NodeOperationsVisitor {
+            type Value = NodeOperations;
 
-        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-            formatter.write_str("Expect Path and Changeset")
-        }
-
-        #[inline]
-        fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
-        where
-            V: MapAccess<'de>,
-        {
-            let mut path: Option<Path> = None;
-            let mut delta_changeset = DeltaChangeset::<V::Error>::new();
-            let mut attribute_changeset = AttributeChangeset::new();
-            while let Some(key) = map.next_key()? {
-                match key {
-                    "delta" => {
-                        if delta_changeset.delta.is_some() {
-                            return Err(de::Error::duplicate_field("delta"));
-                        }
-                        delta_changeset.delta = Some(map.next_value()?);
-                    }
-                    "inverted" => {
-                        if delta_changeset.inverted.is_some() {
-                            return Err(de::Error::duplicate_field("inverted"));
-                        }
-                        delta_changeset.inverted = Some(map.next_value()?);
-                    }
-                    "path" => {
-                        if path.is_some() {
-                            return Err(de::Error::duplicate_field("path"));
-                        }
-                        path = Some(map.next_value::<Path>()?)
-                    }
-                    "new" => {
-                        if attribute_changeset.new.is_some() {
-                            return Err(de::Error::duplicate_field("new"));
-                        }
-                        attribute_changeset.new = Some(map.next_value()?);
-                    }
-                    "old" => {
-                        if attribute_changeset.old.is_some() {
-                            return Err(de::Error::duplicate_field("old"));
-                        }
-                        attribute_changeset.old = Some(map.next_value()?);
-                    }
-                    other => {
-                        tracing::warn!("Unexpected key: {}", other);
-                        panic!()
-                    }
-                }
-            }
-            if path.is_none() {
-                return Err(de::Error::missing_field("path"));
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("Expected node operation")
             }
 
-            let mut changeset: Changeset;
-            if !delta_changeset.is_empty() {
-                changeset = delta_changeset.try_into()?
-            } else {
-                changeset = attribute_changeset.try_into()?;
+            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+            where
+                A: SeqAccess<'de>,
+            {
+                let mut operations = NodeOperations::new();
+                while let Some(operation) = seq.next_element::<NodeOperation>()? {
+                    operations.push_op(operation);
+                }
+                Ok(operations)
             }
-
-            Ok((path.unwrap(), changeset))
-        }
-    }
-    deserializer.deserialize_any(ChangesetVisitor())
-}
-
-struct DeltaChangeset<E> {
-    delta: Option<TextOperations>,
-    inverted: Option<TextOperations>,
-    error: PhantomData<E>,
-}
-
-impl<E> DeltaChangeset<E> {
-    fn new() -> Self {
-        Self {
-            delta: None,
-            inverted: None,
-            error: PhantomData,
-        }
-    }
-
-    fn is_empty(&self) -> bool {
-        self.delta.is_none() && self.inverted.is_none()
-    }
-}
-
-impl<E> std::convert::TryInto<Changeset> for DeltaChangeset<E>
-where
-    E: de::Error,
-{
-    type Error = E;
-
-    fn try_into(self) -> Result<Changeset, Self::Error> {
-        if self.delta.is_none() {
-            return Err(de::Error::missing_field("delta"));
-        }
-
-        if self.inverted.is_none() {
-            return Err(de::Error::missing_field("inverted"));
-        }
-        let changeset = Changeset::Delta {
-            delta: self.delta.unwrap(),
-            inverted: self.inverted.unwrap(),
-        };
-
-        Ok(changeset)
-    }
-}
-struct AttributeChangeset<E> {
-    new: Option<AttributeHashMap>,
-    old: Option<AttributeHashMap>,
-    error: PhantomData<E>,
-}
-
-impl<E> AttributeChangeset<E> {
-    fn new() -> Self {
-        Self {
-            new: Default::default(),
-            old: Default::default(),
-            error: PhantomData,
-        }
-    }
-
-    fn is_empty(&self) -> bool {
-        self.new.is_none() && self.old.is_none()
-    }
-}
-
-impl<E> std::convert::TryInto<Changeset> for AttributeChangeset<E>
-where
-    E: de::Error,
-{
-    type Error = E;
-
-    fn try_into(self) -> Result<Changeset, Self::Error> {
-        if self.new.is_none() {
-            return Err(de::Error::missing_field("new"));
-        }
-
-        if self.old.is_none() {
-            return Err(de::Error::missing_field("old"));
         }
 
-        Ok(Changeset::Attributes {
-            new: self.new.unwrap(),
-            old: self.old.unwrap(),
-        })
+        deserializer.deserialize_any(NodeOperationsVisitor())
     }
 }

+ 1 - 1
shared-lib/lib-ot/src/core/node_tree/path.rs

@@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
 /// The path of  Node A-1 will be [0,0]
 /// The path of  Node A-2 will be [0,1]
 /// The path of  Node B-2 will be [1,1]
-#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug, Default)]
+#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug, Default, Hash)]
 pub struct Path(pub Vec<usize>);
 
 impl Path {

+ 77 - 83
shared-lib/lib-ot/src/core/node_tree/transaction.rs

@@ -1,13 +1,12 @@
 use super::{Changeset, NodeOperations};
-use crate::core::attributes::AttributeHashMap;
 use crate::core::{NodeData, NodeOperation, NodeTree, Path};
 use crate::errors::OTError;
 use indextree::NodeId;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
+
 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 pub struct Transaction {
-    #[serde(flatten)]
     pub operations: NodeOperations,
 
     #[serde(default)]
@@ -37,6 +36,16 @@ impl Transaction {
         Ok(bytes)
     }
 
+    pub fn from_json(s: &str) -> Result<Self, OTError> {
+        let serde_transaction: Transaction = serde_json::from_str(s).map_err(|err| OTError::serde().context(err))?;
+        let mut transaction = Self::new();
+        transaction.extension = serde_transaction.extension;
+        for operation in serde_transaction.operations.into_inner() {
+            transaction.operations.push_op(operation);
+        }
+        Ok(transaction)
+    }
+
     pub fn to_json(&self) -> Result<String, OTError> {
         serde_json::to_string(&self).map_err(|err| OTError::serde().context(err))
     }
@@ -45,6 +54,10 @@ impl Transaction {
         self.operations.into_inner()
     }
 
+    pub fn split(self) -> (Vec<Arc<NodeOperation>>, Extension) {
+        (self.operations.into_inner(), self.extension)
+    }
+
     pub fn push_operation<T: Into<NodeOperation>>(&mut self, operation: T) {
         let operation = operation.into();
         self.operations.push_op(operation);
@@ -57,38 +70,26 @@ impl Transaction {
     pub fn transform(&self, other: &Transaction) -> Result<Transaction, OTError> {
         let mut other = other.clone();
         other.extension = self.extension.clone();
-        for other_operation in other.iter_mut() {
+
+        for other_operation in other.operations.values_mut() {
             let other_operation = Arc::make_mut(other_operation);
-            for operation in self.operations.iter() {
+            for operation in self.operations.values() {
                 operation.transform(other_operation);
             }
         }
+
         Ok(other)
     }
 
     pub fn compose(&mut self, other: Transaction) -> Result<(), OTError> {
         // For the moment, just append `other` operations to the end of `self`.
         let Transaction { operations, extension } = other;
-        self.operations.extend(operations);
+        self.operations.compose(operations);
         self.extension = extension;
         Ok(())
     }
 }
 
-impl std::ops::Deref for Transaction {
-    type Target = Vec<Arc<NodeOperation>>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.operations
-    }
-}
-
-impl std::ops::DerefMut for Transaction {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.operations
-    }
-}
-
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub enum Extension {
     Empty,
@@ -121,18 +122,14 @@ pub struct Position {
     path: Path,
     offset: usize,
 }
-
-pub struct TransactionBuilder<'a> {
-    node_tree: &'a NodeTree,
+#[derive(Default)]
+pub struct TransactionBuilder {
     operations: NodeOperations,
 }
 
-impl<'a> TransactionBuilder<'a> {
-    pub fn new(node_tree: &'a NodeTree) -> TransactionBuilder {
-        TransactionBuilder {
-            node_tree,
-            operations: NodeOperations::default(),
-        }
+impl TransactionBuilder {
+    pub fn new() -> TransactionBuilder {
+        Self::default()
     }
 
     ///
@@ -148,9 +145,9 @@ impl<'a> TransactionBuilder<'a> {
     /// //   0 -- text_1
     /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder};
     /// let mut node_tree = NodeTree::default();
-    /// let transaction = TransactionBuilder::new(&node_tree)
+    /// let transaction = TransactionBuilder::new()
     ///     .insert_nodes_at_path(0,vec![ NodeData::new("text_1")])
-    ///     .finalize();
+    ///     .build();
     ///  node_tree.apply_transaction(transaction).unwrap();
     ///
     ///  node_tree.node_id_at_path(vec![0]).unwrap();
@@ -178,9 +175,9 @@ impl<'a> TransactionBuilder<'a> {
     /// //    |-- text
     /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder};
     /// let mut node_tree = NodeTree::default();
-    /// let transaction = TransactionBuilder::new(&node_tree)
+    /// let transaction = TransactionBuilder::new()
     ///     .insert_node_at_path(0, NodeData::new("text"))
-    ///     .finalize();
+    ///     .build();
     ///  node_tree.apply_transaction(transaction).unwrap();
     /// ```
     ///
@@ -188,49 +185,50 @@ impl<'a> TransactionBuilder<'a> {
         self.insert_nodes_at_path(path, vec![node])
     }
 
-    pub fn update_attributes_at_path(mut self, path: &Path, attributes: AttributeHashMap) -> Self {
-        match self.node_tree.get_node_at_path(path) {
-            Some(node) => {
-                let mut old_attributes = AttributeHashMap::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_op(NodeOperation::Update {
-                    path: path.clone(),
-                    changeset: Changeset::Attributes {
-                        new: attributes,
-                        old: old_attributes,
-                    },
-                });
-            }
-            None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path),
-        }
-        self
-    }
-
-    pub fn update_body_at_path(mut self, path: &Path, changeset: Changeset) -> Self {
-        match self.node_tree.node_id_at_path(path) {
-            Some(_) => {
-                self.operations.push_op(NodeOperation::Update {
-                    path: path.clone(),
-                    changeset,
-                });
-            }
-            None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path),
-        }
+    pub fn update_node_at_path<T: Into<Path>>(mut self, path: T, changeset: Changeset) -> Self {
+        self.operations.push_op(NodeOperation::Update {
+            path: path.into(),
+            changeset,
+        });
         self
     }
-
-    pub fn delete_node_at_path(self, path: &Path) -> Self {
-        self.delete_nodes_at_path(path, 1)
+    //
+    // pub fn update_delta_at_path<T: Into<Path>>(
+    //     mut self,
+    //     path: T,
+    //     new_delta: DeltaTextOperations,
+    // ) -> Result<Self, OTError> {
+    //     let path = path.into();
+    //     let operation: NodeOperation = self
+    //         .operations
+    //         .get(&path)
+    //         .ok_or(Err(OTError::record_not_found().context("Can't found the node")))?;
+    //
+    //     match operation {
+    //         NodeOperation::Insert { path, nodes } => {}
+    //         NodeOperation::Update { path, changeset } => {}
+    //         NodeOperation::Delete { .. } => {}
+    //     }
+    //
+    //     match node.body {
+    //         Body::Empty => Ok(self),
+    //         Body::Delta(delta) => {
+    //             let inverted = new_delta.invert(&delta);
+    //             let changeset = Changeset::Delta {
+    //                 delta: new_delta,
+    //                 inverted,
+    //             };
+    //             Ok(self.update_node_at_path(path, changeset))
+    //         }
+    //     }
+    // }
+
+    pub fn delete_node_at_path(self, node_tree: &NodeTree, path: &Path) -> Self {
+        self.delete_nodes_at_path(node_tree, path, 1)
     }
 
-    pub fn delete_nodes_at_path(mut self, path: &Path, length: usize) -> Self {
-        let node_id = self.node_tree.node_id_at_path(path);
+    pub fn delete_nodes_at_path(mut self, node_tree: &NodeTree, path: &Path, length: usize) -> Self {
+        let node_id = node_tree.node_id_at_path(path);
         if node_id.is_none() {
             tracing::warn!("Path: {:?} doesn't contains any nodes", path);
             return self;
@@ -239,8 +237,8 @@ impl<'a> TransactionBuilder<'a> {
         let mut node_id = node_id.unwrap();
         let mut deleted_nodes = vec![];
         for _ in 0..length {
-            deleted_nodes.push(self.get_deleted_node_data(node_id));
-            node_id = self.node_tree.following_siblings(node_id).next().unwrap();
+            deleted_nodes.push(self.get_deleted_node_data(node_tree, node_id));
+            node_id = node_tree.following_siblings(node_id).next().unwrap();
         }
 
         self.operations.push_op(NodeOperation::Delete {
@@ -250,16 +248,12 @@ impl<'a> TransactionBuilder<'a> {
         self
     }
 
-    fn get_deleted_node_data(&self, node_id: NodeId) -> NodeData {
-        let node_data = self.node_tree.get_node(node_id).unwrap();
-
+    fn get_deleted_node_data(&self, node_tree: &NodeTree, node_id: NodeId) -> NodeData {
+        let node_data = node_tree.get_node(node_id).unwrap();
         let mut children = vec![];
-        self.node_tree
-            .get_children_ids(node_id)
-            .into_iter()
-            .for_each(|child_id| {
-                children.push(self.get_deleted_node_data(child_id));
-            });
+        node_tree.get_children_ids(node_id).into_iter().for_each(|child_id| {
+            children.push(self.get_deleted_node_data(node_tree, child_id));
+        });
 
         NodeData {
             node_type: node_data.node_type.clone(),
@@ -274,7 +268,7 @@ impl<'a> TransactionBuilder<'a> {
         self
     }
 
-    pub fn finalize(self) -> Transaction {
+    pub fn build(self) -> Transaction {
         Transaction::from_operations(self.operations)
     }
 }

+ 94 - 26
shared-lib/lib-ot/src/core/node_tree/tree.rs

@@ -1,6 +1,6 @@
 use super::NodeOperations;
 use crate::core::{Changeset, Node, NodeData, NodeOperation, Path, Transaction};
-use crate::errors::{ErrorBuilder, OTError, OTErrorCode};
+use crate::errors::{OTError, OTErrorCode};
 use indextree::{Arena, FollowingSiblings, NodeId};
 use std::sync::Arc;
 
@@ -20,6 +20,8 @@ impl Default for NodeTree {
     }
 }
 
+pub const PLACEHOLDER_NODE_TYPE: &str = "";
+
 impl NodeTree {
     pub fn new(context: NodeTreeContext) -> NodeTree {
         let mut arena = Arena::new();
@@ -41,7 +43,7 @@ impl NodeTree {
     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);
-        for operation in operations.into_inner().into_iter() {
+        for (_, operation) in operations.into_inner().into_iter().enumerate() {
             let _ = node_tree.apply_op(operation)?;
         }
         Ok(node_tree)
@@ -149,15 +151,15 @@ impl NodeTree {
             return None;
         }
 
-        let mut iterate_node = self.root;
+        let mut node_id = self.root;
         for id in path.iter() {
-            iterate_node = self.child_from_node_at_index(iterate_node, *id)?;
+            node_id = self.node_id_from_parent_at_index(node_id, *id)?;
         }
 
-        if iterate_node.is_removed(&self.arena) {
+        if node_id.is_removed(&self.arena) {
             return None;
         }
-        Some(iterate_node)
+        Some(node_id)
     }
 
     pub fn path_from_node_id(&self, node_id: NodeId) -> Path {
@@ -210,7 +212,7 @@ impl NodeTree {
     /// let node_2 = node_tree.get_node_at_path(&inserted_path).unwrap();
     /// assert_eq!(node_2.node_type, node_1.node_type);
     /// ```
-    pub fn child_from_node_at_index(&self, node_id: NodeId, index: usize) -> Option<NodeId> {
+    pub fn node_id_from_parent_at_index(&self, node_id: NodeId, index: usize) -> Option<NodeId> {
         let children = node_id.children(&self.arena);
         for (counter, child) in children.enumerate() {
             if counter == index {
@@ -240,10 +242,11 @@ impl NodeTree {
     }
 
     pub fn apply_transaction(&mut self, transaction: Transaction) -> Result<(), OTError> {
-        let operations = transaction.into_operations();
+        let operations = transaction.split().0;
         for operation in operations {
             self.apply_op(operation)?;
         }
+
         Ok(())
     }
 
@@ -294,25 +297,68 @@ impl NodeTree {
         if parent_path.is_empty() {
             self.insert_nodes_at_index(self.root, last_index, nodes)
         } else {
-            let parent_node = self
-                .node_id_at_path(parent_path)
-                .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
+            let parent_node = match self.node_id_at_path(parent_path) {
+                None => self.create_adjacent_nodes_for_path(parent_path),
+                Some(parent_node) => parent_node,
+            };
 
             self.insert_nodes_at_index(parent_node, last_index, nodes)
         }
     }
 
+    /// Create the adjacent nodes for the path
+    ///
+    /// It will create a corresponding node for each node on the path if it's not existing.
+    /// If the path is not start from zero, it will create its siblings.
+    ///
+    /// Check out the operation_insert_test.rs for more examples.
+    /// * operation_insert_node_when_its_parent_is_not_exist
+    /// * operation_insert_node_when_multiple_parent_is_not_exist_test
+    ///
+    /// # Arguments
+    ///
+    /// * `path`: creates nodes for this path
+    ///
+    /// returns: NodeId
+    ///
+    fn create_adjacent_nodes_for_path<T: Into<Path>>(&mut self, path: T) -> NodeId {
+        let path = path.into();
+        let mut node_id = self.root;
+        for id in path.iter() {
+            match self.node_id_from_parent_at_index(node_id, *id) {
+                None => {
+                    let num_of_children = node_id.children(&self.arena).count();
+                    if *id > num_of_children {
+                        for _ in 0..(*id - num_of_children) {
+                            let node: Node = placeholder_node().into();
+                            let sibling_node = self.arena.new_node(node);
+                            node_id.append(sibling_node, &mut self.arena);
+                        }
+                    }
+
+                    let node: Node = placeholder_node().into();
+                    let new_node_id = self.arena.new_node(node);
+                    node_id.append(new_node_id, &mut self.arena);
+                    node_id = new_node_id;
+                }
+                Some(next_node_id) => {
+                    node_id = next_node_id;
+                }
+            }
+        }
+        node_id
+    }
+
     /// Inserts nodes before the node with node_id
     ///
     fn insert_nodes_before(&mut self, node_id: &NodeId, nodes: Vec<NodeData>) {
+        if node_id.is_removed(&self.arena) {
+            tracing::warn!("Node:{:?} is remove before insert", node_id);
+            return;
+        }
         for node in nodes {
             let (node, children) = node.split();
             let new_node_id = self.arena.new_node(node);
-            if node_id.is_removed(&self.arena) {
-                tracing::warn!("Node:{:?} is remove before insert", node_id);
-                return;
-            }
-
             node_id.insert_before(new_node_id, &mut self.arena);
             self.append_nodes(&new_node_id, children);
         }
@@ -326,14 +372,21 @@ impl NodeTree {
 
         // Append the node to the end of the children list if index greater or equal to the
         // length of the children.
-        if index >= parent.children(&self.arena).count() {
+        let num_of_children = parent.children(&self.arena).count();
+        if index >= num_of_children {
+            let mut num_of_nodes_to_insert = index - num_of_children;
+            while num_of_nodes_to_insert > 0 {
+                self.append_nodes(&parent, vec![placeholder_node()]);
+                num_of_nodes_to_insert -= 1;
+            }
+
             self.append_nodes(&parent, nodes);
             return Ok(());
         }
 
         let node_to_insert = self
-            .child_from_node_at_index(parent, index)
-            .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
+            .node_id_from_parent_at_index(parent, index)
+            .ok_or_else(|| OTError::internal().context(format!("Can't find the node at {}", index)))?;
 
         self.insert_nodes_before(&node_to_insert, nodes);
         Ok(())
@@ -364,11 +417,22 @@ impl NodeTree {
         Ok(())
     }
 
+    /// Update the node at path with the `changeset`
+    ///
+    /// Do nothing if there is no node at the path.
+    ///
+    /// # Arguments
+    ///
+    /// * `path`: references to the node that will be applied with the changeset  
+    /// * `changeset`: the change that will be applied to the node  
+    ///
+    /// returns: Result<(), OTError>
     fn update(&mut self, path: &Path, changeset: Changeset) -> Result<(), OTError> {
-        self.mut_node_at_path(path, |node| {
-            let _ = node.apply_changeset(changeset)?;
-            Ok(())
-        })
+        match self.mut_node_at_path(path, |node| node.apply_changeset(changeset)) {
+            Ok(_) => {}
+            Err(err) => tracing::error!("{}", err),
+        }
+        Ok(())
     }
 
     fn mut_node_at_path<F>(&mut self, path: &Path, f: F) -> Result<(), OTError>
@@ -378,9 +442,9 @@ impl NodeTree {
         if !path.is_valid() {
             return Err(OTErrorCode::InvalidPath.into());
         }
-        let node_id = self
-            .node_id_at_path(path)
-            .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
+        let node_id = self.node_id_at_path(path).ok_or_else(|| {
+            OTError::path_not_found().context(format!("Can't find the mutated node at path: {:?}", path))
+        })?;
         match self.arena.get_mut(node_id) {
             None => tracing::warn!("The path: {:?} does not contain any nodes", path),
             Some(node) => {
@@ -391,3 +455,7 @@ impl NodeTree {
         Ok(())
     }
 }
+
+pub fn placeholder_node() -> NodeData {
+    NodeData::new(PLACEHOLDER_NODE_TYPE)
+}

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

@@ -38,6 +38,9 @@ impl OTError {
     static_ot_error!(revision_id_conflict, OTErrorCode::RevisionIDConflict);
     static_ot_error!(internal, OTErrorCode::Internal);
     static_ot_error!(serde, OTErrorCode::SerdeError);
+    static_ot_error!(path_not_found, OTErrorCode::PathNotFound);
+    static_ot_error!(compose, OTErrorCode::ComposeOperationFail);
+    static_ot_error!(record_not_found, OTErrorCode::RecordNotFound);
 }
 
 impl fmt::Display for OTError {
@@ -75,7 +78,7 @@ pub enum OTErrorCode {
     PathNotFound,
     PathIsEmpty,
     InvalidPath,
-    UnexpectedEmpty,
+    RecordNotFound,
 }
 
 pub struct ErrorBuilder {

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

@@ -1,4 +1,7 @@
-mod operation_test;
+mod operation_delete_test;
+mod operation_delta_test;
+mod operation_insert_test;
 mod script;
 mod serde_test;
+mod transaction_compose_test;
 mod tree_test;

+ 178 - 0
shared-lib/lib-ot/tests/node/operation_delete_test.rs

@@ -0,0 +1,178 @@
+use crate::node::script::NodeScript::*;
+use crate::node::script::NodeTest;
+
+use lib_ot::core::{Changeset, NodeData, NodeDataBuilder};
+
+#[test]
+fn operation_delete_nested_node_test() {
+    let mut test = NodeTest::new();
+    let image_a = NodeData::new("image_a");
+    let image_b = NodeData::new("image_b");
+
+    let video_a = NodeData::new("video_a");
+    let video_b = NodeData::new("video_b");
+
+    let image_1 = NodeDataBuilder::new("image_1")
+        .add_node_data(image_a.clone())
+        .add_node_data(image_b.clone())
+        .build();
+    let video_1 = NodeDataBuilder::new("video_1")
+        .add_node_data(video_a.clone())
+        .add_node_data(video_b)
+        .build();
+
+    let text_node_1 = NodeDataBuilder::new("text_1")
+        .add_node_data(image_1)
+        .add_node_data(video_1.clone())
+        .build();
+
+    let image_2 = NodeDataBuilder::new("image_2")
+        .add_node_data(image_a)
+        .add_node_data(image_b.clone())
+        .build();
+    let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2).build();
+
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_node_1,
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: text_node_2,
+            rev_id: 2,
+        },
+        // 0:text_1
+        //      0:image_1
+        //             0:image_a
+        //             1:image_b
+        //      1:video_1
+        //             0:video_a
+        //             1:video_b
+        // 1:text_2
+        //      0:image_2
+        //             0:image_a
+        //             1:image_b
+        DeleteNode {
+            path: vec![0, 0, 0].into(),
+            rev_id: 3,
+        },
+        AssertNode {
+            path: vec![0, 0, 0].into(),
+            expected: Some(image_b),
+        },
+        AssertNode {
+            path: vec![0, 1].into(),
+            expected: Some(video_1),
+        },
+        DeleteNode {
+            path: vec![0, 1, 1].into(),
+            rev_id: 4,
+        },
+        AssertNode {
+            path: vec![0, 1, 0].into(),
+            expected: Some(video_a),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_delete_node_with_revision_conflict_test() {
+    let mut test = NodeTest::new();
+    let text_1 = NodeDataBuilder::new("text_1").build();
+    let text_2 = NodeDataBuilder::new("text_2").build();
+    let text_3 = NodeDataBuilder::new("text_3").build();
+
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_1.clone(),
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: text_2,
+            rev_id: 2,
+        },
+        // The node's in the tree will be:
+        // 0: text_1
+        // 2: text_2
+        //
+        // The insert action is happened concurrently with the delete action, because they
+        // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1,
+        // but it was moved to index 2.
+        InsertNode {
+            path: 1.into(),
+            node_data: text_3.clone(),
+            rev_id: 3,
+        },
+        // 0: text_1
+        // 1: text_3
+        // 2: text_2
+        //
+        // The path of the delete action will be transformed to a new path that point to the text_2.
+        // 1 -> 2
+        DeleteNode {
+            path: 1.into(),
+            rev_id: 3,
+        },
+        // After perform the delete action, the tree will be:
+        // 0: text_1
+        // 1: text_3
+        AssertNumberOfChildrenAtPath {
+            path: None,
+            expected: 2,
+        },
+        AssertNode {
+            path: 0.into(),
+            expected: Some(text_1),
+        },
+        AssertNode {
+            path: 1.into(),
+            expected: Some(text_3),
+        },
+        AssertNode {
+            path: 2.into(),
+            expected: None,
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_update_node_after_delete_test() {
+    let mut test = NodeTest::new();
+    let text_1 = NodeDataBuilder::new("text_1").build();
+    let text_2 = NodeDataBuilder::new("text_2").build();
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_1,
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: text_2,
+            rev_id: 2,
+        },
+        DeleteNode {
+            path: 0.into(),
+            rev_id: 3,
+        },
+        // The node at path 1 is not exist. The following UpdateBody script will do nothing
+        AssertNode {
+            path: 1.into(),
+            expected: None,
+        },
+        UpdateBody {
+            path: 1.into(),
+            changeset: Changeset::Delta {
+                delta: Default::default(),
+                inverted: Default::default(),
+            },
+        },
+    ];
+    test.run_scripts(scripts);
+}

+ 41 - 0
shared-lib/lib-ot/tests/node/operation_delta_test.rs

@@ -0,0 +1,41 @@
+use crate::node::script::NodeScript::{AssertNodeDelta, InsertNode, UpdateBody};
+use crate::node::script::{edit_node_delta, NodeTest};
+use lib_ot::core::NodeDataBuilder;
+use lib_ot::text_delta::DeltaTextOperationBuilder;
+
+#[test]
+fn operation_update_delta_test() {
+    let mut test = NodeTest::new();
+    let initial_delta = DeltaTextOperationBuilder::new().build();
+    let new_delta = DeltaTextOperationBuilder::new()
+        .retain(initial_delta.utf16_base_len)
+        .insert("Hello, world")
+        .build();
+    let (changeset, expected) = edit_node_delta(&initial_delta, new_delta);
+    let node = NodeDataBuilder::new("text").insert_delta(initial_delta.clone()).build();
+
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: node,
+            rev_id: 1,
+        },
+        UpdateBody {
+            path: 0.into(),
+            changeset: changeset.clone(),
+        },
+        AssertNodeDelta {
+            path: 0.into(),
+            expected,
+        },
+        UpdateBody {
+            path: 0.into(),
+            changeset: changeset.inverted(),
+        },
+        AssertNodeDelta {
+            path: 0.into(),
+            expected: initial_delta,
+        },
+    ];
+    test.run_scripts(scripts);
+}

+ 460 - 0
shared-lib/lib-ot/tests/node/operation_insert_test.rs

@@ -0,0 +1,460 @@
+use crate::node::script::NodeScript::*;
+use crate::node::script::NodeTest;
+
+use lib_ot::core::{placeholder_node, NodeData, NodeDataBuilder, NodeOperation, Path};
+
+#[test]
+fn operation_insert_op_transform_test() {
+    let node_1 = NodeDataBuilder::new("text_1").build();
+    let node_2 = NodeDataBuilder::new("text_2").build();
+    let op_1 = NodeOperation::Insert {
+        path: Path(vec![0, 1]),
+        nodes: vec![node_1],
+    };
+
+    let mut insert_2 = NodeOperation::Insert {
+        path: Path(vec![0, 1]),
+        nodes: vec![node_2],
+    };
+
+    // let mut node_tree = NodeTree::new("root");
+    // node_tree.apply_op(insert_1.clone()).unwrap();
+
+    op_1.transform(&mut insert_2);
+    let json = serde_json::to_string(&insert_2).unwrap();
+    assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#);
+}
+
+#[test]
+fn operation_insert_one_level_path_test() {
+    let node_data_1 = NodeDataBuilder::new("text_1").build();
+    let node_data_2 = NodeDataBuilder::new("text_2").build();
+    let node_data_3 = NodeDataBuilder::new("text_3").build();
+    let node_3 = node_data_3.clone();
+    //  0: text_1
+    //  1: text_2
+    //
+    //  Insert a new operation with rev_id 2 to index 1,but the index was already taken, so
+    //  it needs to be transformed.
+    //
+    //  0: text_1
+    //  1: text_2
+    //  2: text_3
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: node_data_1.clone(),
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_2.clone(),
+            rev_id: 2,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_3.clone(),
+            rev_id: 2,
+        },
+        AssertNode {
+            path: 2.into(),
+            expected: Some(node_3.clone()),
+        },
+    ];
+    NodeTest::new().run_scripts(scripts);
+
+    //  If the rev_id of the node_data_3 is 3. then the tree will be:
+    //  0: text_1
+    //  1: text_3
+    //  2: text_2
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: node_data_1,
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_2,
+            rev_id: 2,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_3,
+            rev_id: 3,
+        },
+        AssertNode {
+            path: 1.into(),
+            expected: Some(node_3),
+        },
+    ];
+    NodeTest::new().run_scripts(scripts);
+}
+
+#[test]
+fn operation_insert_with_multiple_level_path_test() {
+    let mut test = NodeTest::new();
+    let node_data_1 = NodeDataBuilder::new("text_1")
+        .add_node_data(NodeDataBuilder::new("text_1_1").build())
+        .add_node_data(NodeDataBuilder::new("text_1_2").build())
+        .build();
+
+    let node_data_2 = NodeDataBuilder::new("text_2")
+        .add_node_data(NodeDataBuilder::new("text_2_1").build())
+        .add_node_data(NodeDataBuilder::new("text_2_2").build())
+        .build();
+
+    let node_data_3 = NodeDataBuilder::new("text_3").build();
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: node_data_1,
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_2,
+            rev_id: 2,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_data_3.clone(),
+            rev_id: 2,
+        },
+        AssertNode {
+            path: 2.into(),
+            expected: Some(node_data_3),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_insert_node_out_of_bound_test() {
+    let mut test = NodeTest::new();
+    let image_a = NodeData::new("image_a");
+    let image_b = NodeData::new("image_b");
+    let image = NodeDataBuilder::new("image_1")
+        .add_node_data(image_a)
+        .add_node_data(image_b)
+        .build();
+    let text_node = NodeDataBuilder::new("text_1").add_node_data(image).build();
+    let image_c = NodeData::new("image_c");
+
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_node,
+            rev_id: 1,
+        },
+        // 0:text_1
+        //      0:image_1
+        //             0:image_a
+        //             1:image_b
+        InsertNode {
+            path: vec![0, 0, 3].into(),
+            node_data: image_c.clone(),
+            rev_id: 2,
+        },
+        // 0:text_1
+        //      0:image_1
+        //             0:image_a
+        //             1:image_b
+        //             2:placeholder node
+        //             3:image_c
+        AssertNode {
+            path: vec![0, 0, 2].into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNode {
+            path: vec![0, 0, 3].into(),
+            expected: Some(image_c),
+        },
+        AssertNode {
+            path: vec![0, 0, 10].into(),
+            expected: None,
+        },
+    ];
+    test.run_scripts(scripts);
+}
+#[test]
+fn operation_insert_node_when_parent_is_not_exist_test1() {
+    let mut test = NodeTest::new();
+    let text_1 = NodeDataBuilder::new("text_1").build();
+    let text_2 = NodeDataBuilder::new("text_2").build();
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_1,
+            rev_id: 1,
+        },
+        // The node at path 1 is not existing when inserting the text_2 to path 2.
+        InsertNode {
+            path: 2.into(),
+            node_data: text_2.clone(),
+            rev_id: 2,
+        },
+        AssertNode {
+            path: 1.into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNode {
+            path: 2.into(),
+            expected: Some(text_2),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_insert_node_when_parent_is_not_exist_test2() {
+    let mut test = NodeTest::new();
+    let text_1 = NodeDataBuilder::new("text_1").build();
+    let text_2 = NodeDataBuilder::new("text_2").build();
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_1,
+            rev_id: 1,
+        },
+        // The node at path 1 is not existing when inserting the text_2 to path 2.
+        InsertNode {
+            path: 3.into(),
+            node_data: text_2.clone(),
+            rev_id: 2,
+        },
+        AssertNode {
+            path: 1.into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNode {
+            path: 2.into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNode {
+            path: 3.into(),
+            expected: Some(text_2),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_insert_node_when_its_parent_is_not_exist_test3() {
+    let mut test = NodeTest::new();
+    let text_1 = NodeDataBuilder::new("text_1").build();
+    let text_2 = NodeDataBuilder::new("text_2").build();
+
+    let mut placeholder_node = placeholder_node();
+    placeholder_node.children.push(text_2.clone());
+
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_1,
+            rev_id: 1,
+        },
+        // The node at path 1 is not existing when inserting the text_2 to path 2.
+        InsertNode {
+            path: vec![1, 0].into(),
+            node_data: text_2.clone(),
+            rev_id: 2,
+        },
+        AssertNode {
+            path: 1.into(),
+            expected: Some(placeholder_node),
+        },
+        AssertNode {
+            path: vec![1, 0].into(),
+            expected: Some(text_2),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_insert_node_to_the_end_when_parent_is_not_exist_test() {
+    let mut test = NodeTest::new();
+    let node_0 = NodeData::new("0");
+    let node_1 = NodeData::new("1");
+    let node_1_1 = NodeData::new("1_1");
+    let text_node = NodeData::new("text");
+    let mut ghost = placeholder_node();
+    ghost.children.push(text_node.clone());
+    // 0:0
+    // 1:1
+    //      0:1_1
+    //      1:ghost
+    //              0:text
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: node_0,
+            rev_id: 1,
+        },
+        InsertNode {
+            path: 1.into(),
+            node_data: node_1,
+            rev_id: 2,
+        },
+        InsertNode {
+            path: vec![1, 0].into(),
+            node_data: node_1_1.clone(),
+            rev_id: 3,
+        },
+        InsertNode {
+            path: vec![1, 1, 0].into(),
+            node_data: text_node.clone(),
+            rev_id: 4,
+        },
+        AssertNode {
+            path: vec![1, 0].into(),
+            expected: Some(node_1_1),
+        },
+        AssertNode {
+            path: vec![1, 1].into(),
+            expected: Some(ghost),
+        },
+        AssertNode {
+            path: vec![1, 1, 0].into(),
+            expected: Some(text_node),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+#[test]
+fn operation_insert_node_when_multiple_parent_is_not_exist_test() {
+    let mut test = NodeTest::new();
+    let text_1 = NodeDataBuilder::new("text_1").build();
+    let text_2 = NodeDataBuilder::new("text_2").build();
+
+    let path = vec![1, 0, 0, 0, 0, 0];
+    let mut auto_fill_node = placeholder_node();
+    let mut iter_node: &mut NodeData = &mut auto_fill_node;
+    let insert_path = path.split_at(1).1;
+    for (index, _) in insert_path.iter().enumerate() {
+        if index == insert_path.len() - 1 {
+            iter_node.children.push(text_2.clone());
+        } else {
+            iter_node.children.push(placeholder_node());
+            iter_node = iter_node.children.last_mut().unwrap();
+        }
+    }
+
+    let scripts = vec![
+        InsertNode {
+            path: 0.into(),
+            node_data: text_1,
+            rev_id: 1,
+        },
+        InsertNode {
+            path: path.clone().into(),
+            node_data: text_2.clone(),
+            rev_id: 2,
+        },
+        AssertNode {
+            path: vec![1].into(),
+            expected: Some(auto_fill_node),
+        },
+        AssertNode {
+            path: path.into(),
+            expected: Some(text_2),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_insert_node_when_multiple_parent_is_not_exist_test2() {
+    let mut test = NodeTest::new();
+    // 0:ghost
+    //        0:ghost
+    //        1:ghost
+    //               0:text
+    let mut text_node_parent = placeholder_node();
+    let text_node = NodeDataBuilder::new("text").build();
+    text_node_parent.children.push(text_node.clone());
+
+    let mut ghost = placeholder_node();
+    ghost.children.push(placeholder_node());
+    ghost.children.push(text_node_parent.clone());
+
+    let path = vec![1, 1, 0];
+    let scripts = vec![
+        InsertNode {
+            path: path.into(),
+            node_data: text_node.clone(),
+            rev_id: 1,
+        },
+        // 0:ghost
+        // 1:ghost
+        //        0:ghost
+        //        1:ghost
+        //               0:text
+        AssertNode {
+            path: 0.into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNode {
+            path: 1.into(),
+            expected: Some(ghost),
+        },
+        AssertNumberOfChildrenAtPath {
+            path: Some(1.into()),
+            expected: 2,
+        },
+        AssertNode {
+            path: vec![1, 1].into(),
+            expected: Some(text_node_parent),
+        },
+        AssertNode {
+            path: vec![1, 1, 0].into(),
+            expected: Some(text_node),
+        },
+    ];
+    test.run_scripts(scripts);
+}
+
+#[test]
+fn operation_insert_node_when_multiple_parent_is_not_exist_test3() {
+    let mut test = NodeTest::new();
+    let text_node = NodeDataBuilder::new("text").build();
+    let path = vec![3, 3, 0];
+    let scripts = vec![
+        InsertNode {
+            path: path.clone().into(),
+            node_data: text_node.clone(),
+            rev_id: 1,
+        },
+        // 0:ghost
+        // 1:ghost
+        // 2:ghost
+        // 3:ghost
+        //        0:ghost
+        //        1:ghost
+        //        2:ghost
+        //        3:ghost
+        //               0:text
+        AssertNode {
+            path: 0.into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNode {
+            path: 1.into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNode {
+            path: 2.into(),
+            expected: Some(placeholder_node()),
+        },
+        AssertNumberOfChildrenAtPath {
+            path: Some(3.into()),
+            expected: 4,
+        },
+        AssertNode {
+            path: path.into(),
+            expected: Some(text_node),
+        },
+    ];
+    test.run_scripts(scripts);
+}

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

@@ -1,170 +0,0 @@
-use crate::node::script::NodeScript::*;
-use crate::node::script::NodeTest;
-
-use lib_ot::core::{NodeDataBuilder, NodeOperation, Path};
-
-#[test]
-fn operation_insert_op_transform_test() {
-    let node_1 = NodeDataBuilder::new("text_1").build();
-    let node_2 = NodeDataBuilder::new("text_2").build();
-    let op_1 = NodeOperation::Insert {
-        path: Path(vec![0, 1]),
-        nodes: vec![node_1],
-    };
-
-    let mut insert_2 = NodeOperation::Insert {
-        path: Path(vec![0, 1]),
-        nodes: vec![node_2],
-    };
-
-    // let mut node_tree = NodeTree::new("root");
-    // node_tree.apply_op(insert_1.clone()).unwrap();
-
-    op_1.transform(&mut insert_2);
-    let json = serde_json::to_string(&insert_2).unwrap();
-    assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#);
-}
-
-#[test]
-fn operation_insert_one_level_path_test() {
-    let mut test = NodeTest::new();
-    let node_data_1 = NodeDataBuilder::new("text_1").build();
-    let node_data_2 = NodeDataBuilder::new("text_2").build();
-    let node_data_3 = NodeDataBuilder::new("text_3").build();
-    let node_3 = node_data_3.clone();
-    //  0: text_1
-    //  1: text_2
-    //
-    //  Insert a new operation with rev_id 1,but the rev_id:1 is already exist, so
-    //  it needs to be transformed.
-    //  1:text_3 => 2:text_3
-    //
-    //  0: text_1
-    //  1: text_2
-    //  2: text_3
-    //
-    //  If the rev_id of the insert operation is 3. then the tree will be:
-    //  0: text_1
-    //  1: text_3
-    //  2: text_2
-    let scripts = vec![
-        InsertNode {
-            path: 0.into(),
-            node_data: node_data_1,
-            rev_id: 1,
-        },
-        InsertNode {
-            path: 1.into(),
-            node_data: node_data_2,
-            rev_id: 2,
-        },
-        InsertNode {
-            path: 1.into(),
-            node_data: node_data_3,
-            rev_id: 1,
-        },
-        AssertNode {
-            path: 2.into(),
-            expected: Some(node_3),
-        },
-    ];
-    test.run_scripts(scripts);
-}
-
-#[test]
-fn operation_insert_with_multiple_level_path_test() {
-    let mut test = NodeTest::new();
-    let node_data_1 = NodeDataBuilder::new("text_1")
-        .add_node_data(NodeDataBuilder::new("text_1_1").build())
-        .add_node_data(NodeDataBuilder::new("text_1_2").build())
-        .build();
-
-    let node_data_2 = NodeDataBuilder::new("text_2")
-        .add_node_data(NodeDataBuilder::new("text_2_1").build())
-        .add_node_data(NodeDataBuilder::new("text_2_2").build())
-        .build();
-
-    let node_data_3 = NodeDataBuilder::new("text_3").build();
-    let scripts = vec![
-        InsertNode {
-            path: 0.into(),
-            node_data: node_data_1,
-            rev_id: 1,
-        },
-        InsertNode {
-            path: 1.into(),
-            node_data: node_data_2,
-            rev_id: 2,
-        },
-        InsertNode {
-            path: 1.into(),
-            node_data: node_data_3.clone(),
-            rev_id: 1,
-        },
-        AssertNode {
-            path: 2.into(),
-            expected: Some(node_data_3),
-        },
-    ];
-    test.run_scripts(scripts);
-}
-
-#[test]
-fn operation_delete_test() {
-    let mut test = NodeTest::new();
-    let node_data_1 = NodeDataBuilder::new("text_1").build();
-    let node_data_2 = NodeDataBuilder::new("text_2").build();
-    let node_data_3 = NodeDataBuilder::new("text_3").build();
-    let node_3 = node_data_3.clone();
-
-    let scripts = vec![
-        InsertNode {
-            path: 0.into(),
-            node_data: node_data_1,
-            rev_id: 1,
-        },
-        InsertNode {
-            path: 1.into(),
-            node_data: node_data_2,
-            rev_id: 2,
-        },
-        // The node's in the tree will be:
-        // 0: text_1
-        // 2: text_2
-        //
-        // The insert action is happened concurrently with the delete action, because they
-        // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1,
-        // but it was moved to index 2.
-        InsertNode {
-            path: 1.into(),
-            node_data: node_data_3,
-            rev_id: 3,
-        },
-        // 0: text_1
-        // 1: text_3
-        // 2: text_2
-        //
-        // The path of the delete action will be transformed to a new path that point to the text_2.
-        // 1 -> 2
-        DeleteNode {
-            path: 1.into(),
-            rev_id: 3,
-        },
-        // After perform the delete action, the tree will be:
-        // 0: text_1
-        // 1: text_3
-        AssertNumberOfChildrenAtPath {
-            path: None,
-            expected: 2,
-        },
-        AssertNode {
-            path: 1.into(),
-            expected: Some(node_3),
-        },
-        AssertNode {
-            path: 2.into(),
-            expected: None,
-        },
-    ];
-    test.run_scripts(scripts);
-}

+ 51 - 15
shared-lib/lib-ot/tests/node/script.rs

@@ -1,5 +1,6 @@
 #![allow(clippy::all)]
-use lib_ot::core::{NodeTreeContext, Transaction};
+use lib_ot::core::{NodeTreeContext, OperationTransform, Transaction};
+use lib_ot::text_delta::DeltaTextOperationBuilder;
 use lib_ot::{
     core::attributes::AttributeHashMap,
     core::{Body, Changeset, NodeData, NodeTree, Path, TransactionBuilder},
@@ -84,9 +85,7 @@ impl NodeTest {
                 node_data: node,
                 rev_id,
             } => {
-                let mut transaction = TransactionBuilder::new(&self.node_tree)
-                    .insert_node_at_path(path, node)
-                    .finalize();
+                let mut transaction = TransactionBuilder::new().insert_node_at_path(path, node).build();
                 self.transform_transaction_if_need(&mut transaction, rev_id);
                 self.apply_transaction(transaction);
             }
@@ -95,29 +94,34 @@ impl NodeTest {
                 node_data_list,
                 rev_id,
             } => {
-                let mut transaction = TransactionBuilder::new(&self.node_tree)
+                let mut transaction = TransactionBuilder::new()
                     .insert_nodes_at_path(path, node_data_list)
-                    .finalize();
+                    .build();
                 self.transform_transaction_if_need(&mut transaction, rev_id);
                 self.apply_transaction(transaction);
             }
             NodeScript::UpdateAttributes { path, attributes } => {
-                let transaction = TransactionBuilder::new(&self.node_tree)
-                    .update_attributes_at_path(&path, attributes)
-                    .finalize();
+                let node = self.node_tree.get_node_data_at_path(&path).unwrap();
+                let transaction = TransactionBuilder::new()
+                    .update_node_at_path(
+                        &path,
+                        Changeset::Attributes {
+                            new: attributes,
+                            old: node.attributes,
+                        },
+                    )
+                    .build();
                 self.apply_transaction(transaction);
             }
             NodeScript::UpdateBody { path, changeset } => {
                 //
-                let transaction = TransactionBuilder::new(&self.node_tree)
-                    .update_body_at_path(&path, changeset)
-                    .finalize();
+                let transaction = TransactionBuilder::new().update_node_at_path(&path, changeset).build();
                 self.apply_transaction(transaction);
             }
             NodeScript::DeleteNode { path, rev_id } => {
-                let mut transaction = TransactionBuilder::new(&self.node_tree)
-                    .delete_node_at_path(&path)
-                    .finalize();
+                let mut transaction = TransactionBuilder::new()
+                    .delete_node_at_path(&self.node_tree, &path)
+                    .build();
                 self.transform_transaction_if_need(&mut transaction, rev_id);
                 self.apply_transaction(transaction);
             }
@@ -175,3 +179,35 @@ impl NodeTest {
         }
     }
 }
+
+pub fn edit_node_delta(
+    delta: &DeltaTextOperations,
+    new_delta: DeltaTextOperations,
+) -> (Changeset, DeltaTextOperations) {
+    let inverted = new_delta.invert(&delta);
+    let expected = delta.compose(&new_delta).unwrap();
+    let changeset = Changeset::Delta {
+        delta: new_delta.clone(),
+        inverted: inverted.clone(),
+    };
+    (changeset, expected)
+}
+
+pub fn make_node_delta_changeset(
+    initial_content: &str,
+    insert_str: &str,
+) -> (DeltaTextOperations, Changeset, DeltaTextOperations) {
+    let initial_content = initial_content.to_owned();
+    let initial_delta = DeltaTextOperationBuilder::new().insert(&initial_content).build();
+    let delta = DeltaTextOperationBuilder::new()
+        .retain(initial_content.len())
+        .insert(insert_str)
+        .build();
+    let inverted = delta.invert(&initial_delta);
+    let expected = initial_delta.compose(&delta).unwrap();
+    let changeset = Changeset::Delta {
+        delta: delta.clone(),
+        inverted: inverted.clone(),
+    };
+    (initial_delta, changeset, expected)
+}

+ 28 - 30
shared-lib/lib-ot/tests/node/serde_test.rs

@@ -1,6 +1,4 @@
-use lib_ot::core::{
-    AttributeBuilder, Changeset, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path, Transaction,
-};
+use lib_ot::core::{AttributeBuilder, Changeset, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path};
 use lib_ot::text_delta::DeltaTextOperationBuilder;
 
 #[test]
@@ -69,33 +67,33 @@ fn operation_update_node_body_deserialize_test() {
     assert_eq!(json_1, json_2);
 }
 
-#[test]
-fn transaction_serialize_test() {
-    let insert = NodeOperation::Insert {
-        path: Path(vec![0, 1]),
-        nodes: vec![NodeData::new("text".to_owned())],
-    };
-    let transaction = Transaction::from_operations(vec![insert]);
-    let json = serde_json::to_string(&transaction).unwrap();
-    assert_eq!(
-        json,
-        r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}]}"#
-    );
-}
-
-#[test]
-fn transaction_deserialize_test() {
-    let json = r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}},"after_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}}}}"#;
-
-    let transaction: Transaction = serde_json::from_str(json).unwrap();
-    assert_eq!(transaction.operations.len(), 1);
-}
-
-#[test]
-fn node_tree_deserialize_test() {
-    let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap();
-    assert_eq!(tree.number_of_children(None), 1);
-}
+// #[test]
+// fn transaction_serialize_test() {
+//     let insert = NodeOperation::Insert {
+//         path: Path(vec![0, 1]),
+//         nodes: vec![NodeData::new("text".to_owned())],
+//     };
+//     let transaction = Transaction::from_operations(vec![insert]);
+//     let json = serde_json::to_string(&transaction).unwrap();
+//     assert_eq!(
+//         json,
+//         r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}]}"#
+//     );
+// }
+//
+// #[test]
+// fn transaction_deserialize_test() {
+//     let json = r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}},"after_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}}}}"#;
+//
+//     let transaction: Transaction = serde_json::from_str(json).unwrap();
+//     assert_eq!(transaction.operations.len(), 1);
+// }
+//
+// #[test]
+// fn node_tree_deserialize_test() {
+//     let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap();
+//     assert_eq!(tree.number_of_children(None), 1);
+// }
 
 #[test]
 fn node_tree_serialize_test() {

+ 104 - 0
shared-lib/lib-ot/tests/node/transaction_compose_test.rs

@@ -0,0 +1,104 @@
+use crate::node::script::{edit_node_delta, make_node_delta_changeset};
+use lib_ot::core::{AttributeEntry, Changeset, NodeDataBuilder, NodeOperation, Transaction, TransactionBuilder};
+use lib_ot::text_delta::DeltaTextOperationBuilder;
+
+#[test]
+fn transaction_compose_update_after_insert_test() {
+    let (initial_delta, changeset, _) = make_node_delta_changeset("Hello", " world");
+    let node_data = NodeDataBuilder::new("text").insert_delta(initial_delta).build();
+
+    // Modify the same path, the operations will be merged after composing if possible.
+    let mut transaction_a = TransactionBuilder::new().insert_node_at_path(0, node_data).build();
+    let transaction_b = TransactionBuilder::new().update_node_at_path(0, changeset).build();
+    let _ = transaction_a.compose(transaction_b).unwrap();
+
+    // The operations are merged into one operation
+    assert_eq!(transaction_a.operations.len(), 1);
+    assert_eq!(
+        transaction_a.to_json().unwrap(),
+        r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world"}]}}]}]}"#
+    );
+}
+
+#[test]
+fn transaction_compose_multiple_update_test() {
+    let (initial_delta, changeset_1, final_delta) = make_node_delta_changeset("Hello", " world");
+    let mut transaction = TransactionBuilder::new()
+        .insert_node_at_path(0, NodeDataBuilder::new("text").insert_delta(initial_delta).build())
+        .build();
+    let (changeset_2, _) = edit_node_delta(
+        &final_delta,
+        DeltaTextOperationBuilder::new()
+            .retain(final_delta.utf16_target_len)
+            .insert("😁")
+            .build(),
+    );
+
+    let mut other_transaction = Transaction::new();
+
+    // the following two update operations will be merged into one
+    let update_1 = TransactionBuilder::new().update_node_at_path(0, changeset_1).build();
+    other_transaction.compose(update_1).unwrap();
+
+    let update_2 = TransactionBuilder::new().update_node_at_path(0, changeset_2).build();
+    other_transaction.compose(update_2).unwrap();
+
+    let inverted = Transaction::from_operations(other_transaction.operations.inverted());
+
+    // the update operation will be merged into insert operation
+    let _ = transaction.compose(other_transaction).unwrap();
+    assert_eq!(transaction.operations.len(), 1);
+    assert_eq!(
+        transaction.to_json().unwrap(),
+        r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world😁"}]}}]}]}"#
+    );
+
+    let _ = transaction.compose(inverted).unwrap();
+    assert_eq!(
+        transaction.to_json().unwrap(),
+        r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]}]}"#
+    );
+}
+
+#[test]
+fn transaction_compose_multiple_attribute_test() {
+    let delta = DeltaTextOperationBuilder::new().insert("Hello").build();
+    let node = NodeDataBuilder::new("text").insert_delta(delta).build();
+
+    let insert_operation = NodeOperation::Insert {
+        path: 0.into(),
+        nodes: vec![node],
+    };
+
+    let mut transaction = Transaction::new();
+    transaction.push_operation(insert_operation);
+
+    let new_attribute = AttributeEntry::new("subtype", "bulleted-list");
+    let update_operation = NodeOperation::Update {
+        path: 0.into(),
+        changeset: Changeset::Attributes {
+            new: new_attribute.clone().into(),
+            old: Default::default(),
+        },
+    };
+    transaction.push_operation(update_operation);
+    assert_eq!(
+        transaction.to_json().unwrap(),
+        r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"bulleted-list"},"old":{}}}}]}"#
+    );
+
+    let old_attribute = new_attribute;
+    let new_attribute = AttributeEntry::new("subtype", "number-list");
+    transaction.push_operation(NodeOperation::Update {
+        path: 0.into(),
+        changeset: Changeset::Attributes {
+            new: new_attribute.into(),
+            old: old_attribute.into(),
+        },
+    });
+
+    assert_eq!(
+        transaction.to_json().unwrap(),
+        r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"number-list"},"old":{"subtype":"bulleted-list"}}}}]}"#
+    );
+}

+ 19 - 111
shared-lib/lib-ot/tests/node/tree_test.rs

@@ -1,10 +1,7 @@
 use crate::node::script::NodeScript::*;
-use crate::node::script::NodeTest;
-use lib_ot::core::Body;
-use lib_ot::core::Changeset;
-use lib_ot::core::OperationTransform;
+use crate::node::script::{make_node_delta_changeset, NodeTest};
+
 use lib_ot::core::{NodeData, NodeDataBuilder, Path};
-use lib_ot::text_delta::{DeltaTextOperationBuilder, DeltaTextOperations};
 
 #[test]
 fn node_insert_test() {
@@ -37,71 +34,6 @@ fn node_insert_with_empty_path_test() {
     test.run_scripts(scripts);
 }
 
-#[test]
-#[should_panic]
-fn node_insert_with_not_exist_path_test() {
-    let mut test = NodeTest::new();
-    let node_data = NodeData::new("text");
-    let path: Path = vec![0, 0, 9].into();
-    let scripts = vec![
-        InsertNode {
-            path: path.clone(),
-            node_data: node_data.clone(),
-            rev_id: 1,
-        },
-        AssertNode {
-            path,
-            expected: Some(node_data),
-        },
-    ];
-    test.run_scripts(scripts);
-}
-
-#[test]
-// Append the node to the end of the list if the insert path is out of bounds.
-fn node_insert_out_of_bound_test() {
-    let mut test = NodeTest::new();
-    let image_a = NodeData::new("image_a");
-    let image_b = NodeData::new("image_b");
-    let image = NodeDataBuilder::new("image_1")
-        .add_node_data(image_a)
-        .add_node_data(image_b)
-        .build();
-    let text_node = NodeDataBuilder::new("text_1").add_node_data(image).build();
-    let image_c = NodeData::new("image_c");
-
-    let scripts = vec![
-        InsertNode {
-            path: 0.into(),
-            node_data: text_node,
-            rev_id: 1,
-        },
-        // 0:text_1
-        //      0:image_1
-        //             0:image_a
-        //             1:image_b
-        InsertNode {
-            path: vec![0, 0, 10].into(),
-            node_data: image_c.clone(),
-            rev_id: 2,
-        },
-        // 0:text_1
-        //      0:image_1
-        //             0:image_a
-        //             1:image_b
-        //             2:image_b
-        AssertNode {
-            path: vec![0, 0, 2].into(),
-            expected: Some(image_c),
-        },
-        AssertNode {
-            path: vec![0, 0, 10].into(),
-            expected: None,
-        },
-    ];
-    test.run_scripts(scripts);
-}
-
 #[test]
 fn tree_insert_multiple_nodes_at_root_path_test() {
     let mut test = NodeTest::new();
@@ -438,11 +370,11 @@ fn node_delete_node_from_list_test() {
         InsertNode {
             path: 1.into(),
             node_data: text_node_2.clone(),
-            rev_id: 1,
+            rev_id: 2,
         },
         DeleteNode {
             path: 0.into(),
-            rev_id: 2,
+            rev_id: 3,
         },
         AssertNode {
             path: 1.into(),
@@ -487,7 +419,7 @@ fn node_delete_nested_node_test() {
         InsertNode {
             path: 1.into(),
             node_data: text_node_2,
-            rev_id: 1,
+            rev_id: 2,
         },
         // 0:text_1
         //      0:image_1
@@ -499,7 +431,7 @@ fn node_delete_nested_node_test() {
         //             1:image_b
         DeleteNode {
             path: vec![0, 0, 0].into(),
-            rev_id: 2,
+            rev_id: 3,
         },
         // 0:text_1
         //      0:image_1
@@ -514,7 +446,7 @@ fn node_delete_nested_node_test() {
         },
         DeleteNode {
             path: vec![0, 0].into(),
-            rev_id: 3,
+            rev_id: 4,
         },
         // 0:text_1
         // 1:text_2
@@ -676,12 +608,16 @@ fn node_reorder_nodes_test() {
         //             1:image_b
         DeleteNode {
             path: vec![0].into(),
-            rev_id: 2,
+            rev_id: 3,
+        },
+        AssertNode {
+            path: vec![0].into(),
+            expected: Some(text_node_2.clone()),
         },
         InsertNode {
             path: vec![1].into(),
             node_data: text_node_1.clone(),
-            rev_id: 3,
+            rev_id: 4,
         },
         // 0:text_2
         //      0:image_2
@@ -722,10 +658,8 @@ fn node_reorder_nodes_test() {
 #[test]
 fn node_update_body_test() {
     let mut test = NodeTest::new();
-    let (initial_delta, changeset, _, expected) = make_node_delta_changeset("Hello", "AppFlowy");
-    let node = NodeDataBuilder::new("text")
-        .insert_body(Body::Delta(initial_delta))
-        .build();
+    let (initial_delta, changeset, expected) = make_node_delta_changeset("Hello", "AppFlowy");
+    let node = NodeDataBuilder::new("text").insert_delta(initial_delta).build();
 
     let scripts = vec![
         InsertNode {
@@ -748,10 +682,8 @@ fn node_update_body_test() {
 #[test]
 fn node_inverted_body_changeset_test() {
     let mut test = NodeTest::new();
-    let (initial_delta, changeset, inverted_changeset, _expected) = make_node_delta_changeset("Hello", "AppFlowy");
-    let node = NodeDataBuilder::new("text")
-        .insert_body(Body::Delta(initial_delta.clone()))
-        .build();
+    let (initial_delta, changeset, _expected) = make_node_delta_changeset("Hello", "AppFlowy");
+    let node = NodeDataBuilder::new("text").insert_delta(initial_delta.clone()).build();
 
     let scripts = vec![
         InsertNode {
@@ -761,11 +693,11 @@ fn node_inverted_body_changeset_test() {
         },
         UpdateBody {
             path: 0.into(),
-            changeset,
+            changeset: changeset.clone(),
         },
         UpdateBody {
             path: 0.into(),
-            changeset: inverted_changeset,
+            changeset: changeset.inverted(),
         },
         AssertNodeDelta {
             path: 0.into(),
@@ -774,27 +706,3 @@ fn node_inverted_body_changeset_test() {
     ];
     test.run_scripts(scripts);
 }
-
-fn make_node_delta_changeset(
-    initial_content: &str,
-    insert_str: &str,
-) -> (DeltaTextOperations, Changeset, Changeset, DeltaTextOperations) {
-    let initial_content = initial_content.to_owned();
-    let initial_delta = DeltaTextOperationBuilder::new().insert(&initial_content).build();
-    let delta = DeltaTextOperationBuilder::new()
-        .retain(initial_content.len())
-        .insert(insert_str)
-        .build();
-    let inverted = delta.invert(&initial_delta);
-    let expected = initial_delta.compose(&delta).unwrap();
-
-    let changeset = Changeset::Delta {
-        delta: delta.clone(),
-        inverted: inverted.clone(),
-    };
-    let inverted_changeset = Changeset::Delta {
-        delta: inverted,
-        inverted: delta,
-    };
-    (initial_delta, changeset, inverted_changeset, expected)
-}