diff --git a/crates/fuzz/src/actor.rs b/crates/fuzz/src/actor.rs index 0c5113c7..5b380481 100644 --- a/crates/fuzz/src/actor.rs +++ b/crates/fuzz/src/actor.rs @@ -479,64 +479,41 @@ struct Node { position: String, } -struct FlatNode { - id: String, - parent: Option, - meta: FxHashMap, - index: usize, - position: String, -} - -impl FlatNode { - fn from_loro_value(value: &LoroValue) -> Self { - let map = value.as_map().unwrap(); - let id = map.get("id").unwrap().as_string().unwrap().to_string(); - let parent = map - .get("parent") - .unwrap() - .as_string() - .map(|x| x.to_string()); - - let meta = map.get("meta").unwrap().as_map().unwrap().as_ref().clone(); - let index = *map.get("index").unwrap().as_i64().unwrap() as usize; - let position = map - .get("fractional_index") - .unwrap() - .as_string() - .unwrap() - .to_string(); - FlatNode { - id, - parent, - meta, - index, - position, - } - } -} - impl Node { fn from_loro_value(value: &[LoroValue]) -> Vec { let mut node_map = FxHashMap::default(); let mut parent_child_map = FxHashMap::default(); + for node in value.iter() { + let map = node.as_map().unwrap(); + let id = map.get("id").unwrap().as_string().unwrap().to_string(); + let parent = map + .get("parent") + .unwrap() + .as_string() + .map(|x| x.to_string()); - for flat_node in value.iter() { - let flat_node = FlatNode::from_loro_value(flat_node); + let meta = map.get("meta").unwrap().as_map().unwrap().as_ref().clone(); + let index = *map.get("index").unwrap().as_i64().unwrap() as usize; + let position = map + .get("fractional_index") + .unwrap() + .as_string() + .unwrap() + .to_string(); + let children = map.get("children").unwrap().as_list().unwrap(); + let children = Node::from_loro_value(children); let tree_node = Node { - // id: flat_node.id.clone(), - // parent: flat_node.parent.clone(), - children: vec![], - meta: flat_node.meta, - // index: flat_node.index, - position: flat_node.position, + children, + meta, + position, }; - node_map.insert(flat_node.id.clone(), tree_node); + node_map.insert(id.clone(), tree_node); parent_child_map - .entry(flat_node.parent) + .entry(parent) .or_insert_with(Vec::new) - .push((flat_node.index, flat_node.id)); + .push((index, id)); } let mut node_map_clone = node_map.clone(); for (parent_id, child_ids) in parent_child_map.iter() { diff --git a/crates/fuzz/src/container/tree.rs b/crates/fuzz/src/container/tree.rs index 9eb563b0..702b94db 100644 --- a/crates/fuzz/src/container/tree.rs +++ b/crates/fuzz/src/container/tree.rs @@ -488,18 +488,9 @@ impl ApplyDiff for TreeTracker { fn to_value(&self) -> LoroValue { let mut list: Vec> = Vec::new(); - let mut q = VecDeque::from_iter( - self.tree - .iter() - .sorted_unstable_by_key(|x| &x.position) - .enumerate(), - ); - - while let Some((i, node)) = q.pop_front() { + for (i, node) in self.tree.iter().enumerate() { list.push(node.to_value(i)); - q.extend(node.children.iter().enumerate()); } - list.into() } } @@ -537,6 +528,15 @@ impl TreeNode { ); map.insert("fractional_index".to_string(), self.position.clone().into()); map.insert("index".to_string(), (index as i64).into()); + map.insert( + "children".to_string(), + self.children + .iter() + .enumerate() + .map(|(i, n)| n.to_value(i)) + .collect::>() + .into(), + ); map } } diff --git a/crates/fuzz/tests/compatibility.rs b/crates/fuzz/tests/compatibility.rs index 9caf9b0d..7beec786 100644 --- a/crates/fuzz/tests/compatibility.rs +++ b/crates/fuzz/tests/compatibility.rs @@ -30,8 +30,21 @@ fn updates_with_commit_message_can_be_imported_to_016() { let doc2 = loro_016::LoroDoc::new(); doc2.import(&doc1.export_snapshot()).unwrap(); assert_eq!( - doc2.get_deep_value().to_json(), - doc1.get_deep_value().to_json() + doc2.get_text("text").to_string(), + doc1.get_text("text").to_string() + ); + + assert_eq!( + doc2.get_tree("tree") + .nodes() + .into_iter() + .map(|x| x.to_string()) + .collect::>(), + doc1.get_tree("tree") + .nodes() + .into_iter() + .map(|x| x.to_string()) + .collect::>() ); { diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index 96e4e53c..a105e3e8 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -4016,7 +4016,7 @@ mod test { .unwrap(); assert_eq!(meta, 123.into()); assert_eq!( - r#"[{"parent":null,"meta":{"a":123},"id":"0@1","index":0,"fractional_index":"80"}]"#, + r#"[{"parent":null,"meta":{"a":123},"id":"0@1","index":0,"children":[],"fractional_index":"80"}]"#, tree.get_deep_value().to_json() ); let bytes = loro.export_snapshot(); diff --git a/crates/loro-internal/src/handler/tree.rs b/crates/loro-internal/src/handler/tree.rs index 39127403..8d7de423 100644 --- a/crates/loro-internal/src/handler/tree.rs +++ b/crates/loro-internal/src/handler/tree.rs @@ -11,7 +11,7 @@ use smallvec::smallvec; use crate::{ container::tree::tree_op::TreeOp, delta::{TreeDiffItem, TreeExternalDiff}, - state::{FractionalIndexGenResult, NodePosition, TreeParentId}, + state::{FractionalIndexGenResult, NodePosition, TreeNode, TreeNodeWithChildren, TreeParentId}, txn::{EventHint, Transaction}, BasicHandler, HandlerTrait, MapHandler, }; @@ -816,10 +816,34 @@ impl TreeHandler { } } + pub fn get_nodes_under(&self, parent: TreeParentId) -> Vec { + match &self.inner { + MaybeDetached::Detached(_t) => { + unreachable!() + } + MaybeDetached::Attached(a) => a.with_state(|state| { + let a = state.as_tree_state().unwrap(); + a.get_all_tree_nodes_under(parent) + }), + } + } pub fn roots(&self) -> Vec { self.children(&TreeParentId::Root).unwrap_or_default() } + pub fn get_all_hierarchy_nodes_under(&self, parent: TreeParentId) -> Vec { + match &self.inner { + MaybeDetached::Detached(_t) => { + // TODO: implement + unimplemented!() + } + MaybeDetached::Attached(a) => a.with_state(|state| { + let a = state.as_tree_state().unwrap(); + a.get_all_hierarchy_nodes_under(parent) + }), + } + } + #[allow(non_snake_case)] pub fn __internal__next_tree_id(&self) -> TreeID { match &self.inner { diff --git a/crates/loro-internal/src/history_cache.rs b/crates/loro-internal/src/history_cache.rs index af6646e1..437f087d 100644 --- a/crates/loro-internal/src/history_cache.rs +++ b/crates/loro-internal/src/history_cache.rs @@ -264,7 +264,7 @@ impl ContainerHistoryCache { op: Arc::new(TreeOp::Create { target: node.id, parent: node.parent.tree_id(), - position: node.position.clone(), + position: node.fractional_index.clone(), }), effected: true, }) diff --git a/crates/loro-internal/src/lib.rs b/crates/loro-internal/src/lib.rs index 5fc768cf..e3fe82ea 100644 --- a/crates/loro-internal/src/lib.rs +++ b/crates/loro-internal/src/lib.rs @@ -21,7 +21,7 @@ pub use loro::LoroDoc; pub use loro_common; pub use oplog::OpLog; pub use state::DocState; -pub use state::TreeParentId; +pub use state::{TreeNode, TreeNodeWithChildren, TreeParentId}; pub use undo::UndoManager; pub use utils::subscription::Subscription; pub mod awareness; @@ -70,6 +70,7 @@ pub(crate) use loro_common::InternalString; pub use container::ContainerType; pub use encoding::json_schema::json; +pub use fractional_index::FractionalIndex; pub use loro_common::{loro_value, to_value}; #[cfg(feature = "wasm")] pub use value::wasm; diff --git a/crates/loro-internal/src/state.rs b/crates/loro-internal/src/state.rs index a46d6de9..fba87b6a 100644 --- a/crates/loro-internal/src/state.rs +++ b/crates/loro-internal/src/state.rs @@ -1,6 +1,5 @@ use std::{ borrow::Cow, - f32::consts::E, io::Write, sync::{ atomic::{AtomicU64, Ordering}, @@ -52,8 +51,8 @@ pub(crate) use container_store::GcStore; pub(crate) use list_state::ListState; pub(crate) use map_state::MapState; pub(crate) use richtext_state::RichtextState; -pub use tree_state::TreeParentId; pub(crate) use tree_state::{get_meta_value, FractionalIndexGenResult, NodePosition, TreeState}; +pub use tree_state::{TreeNode, TreeNodeWithChildren, TreeParentId}; use self::{container_store::ContainerWrapper, unknown_state::UnknownState}; @@ -1028,27 +1027,29 @@ impl DocState { match value { LoroValue::Container(_) => unreachable!(), LoroValue::List(mut list) => { - // FIXME: doesn't work for tree - if list.iter().all(|x| !x.is_container()) { - return LoroValue::Map(Arc::new(fx_map!( - "cid".into() => cid_str, - "value".into() => LoroValue::List(list) - ))); - } + if container.get_type() == ContainerType::Tree { + get_meta_value(Arc::make_mut(&mut list), self); + } else { + if list.iter().all(|x| !x.is_container()) { + return LoroValue::Map(Arc::new(fx_map!( + "cid".into() => cid_str, + "value".into() => LoroValue::List(list) + ))); + } - let list_mut = Arc::make_mut(&mut list); - for item in list_mut.iter_mut() { - if item.is_container() { - let container = item.as_container().unwrap(); - let container_idx = self.arena.register_container(container); - let value = self.get_container_deep_value_with_id( - container_idx, - Some(container.clone()), - ); - *item = value; + let list_mut = Arc::make_mut(&mut list); + for item in list_mut.iter_mut() { + if item.is_container() { + let container = item.as_container().unwrap(); + let container_idx = self.arena.register_container(container); + let value = self.get_container_deep_value_with_id( + container_idx, + Some(container.clone()), + ); + *item = value; + } } } - LoroValue::Map(Arc::new(fx_map!( "cid".into() => cid_str, "value".into() => LoroValue::List(list) diff --git a/crates/loro-internal/src/state/tree_state.rs b/crates/loro-internal/src/state/tree_state.rs index 56d3f42b..3b405609 100644 --- a/crates/loro-internal/src/state/tree_state.rs +++ b/crates/loro-internal/src/state/tree_state.rs @@ -719,15 +719,18 @@ impl TreeState { !self.trees.contains_key(target) } + // Get all flat tree nodes, excluding deleted nodes. pub(crate) fn tree_nodes(&self) -> Vec { self.get_all_tree_nodes_under(TreeParentId::Root) } + // Get all flat deleted nodes #[allow(unused)] pub(crate) fn deleted_tree_nodes(&self) -> Vec { self.get_all_tree_nodes_under(TreeParentId::Deleted) } + // Get all flat tree nodes under the parent pub(crate) fn get_all_tree_nodes_under(&self, root: TreeParentId) -> Vec { let mut ans = vec![]; let children = self.children.get(&root); @@ -739,7 +742,7 @@ impl TreeState { ans.push(TreeNode { id: target, parent, - position: position.position.clone(), + fractional_index: position.position.clone(), index, last_move_op: self.trees.get(&target).map(|x| x.last_move_op).unwrap(), }); @@ -757,6 +760,26 @@ impl TreeState { ans } + pub(crate) fn get_all_hierarchy_nodes_under( + &self, + root: TreeParentId, + ) -> Vec { + let mut ans = vec![]; + let Some(children) = self.children.get(&root) else { + return ans; + }; + for (index, (position, &target)) in children.iter().enumerate() { + ans.push(TreeNodeWithChildren { + id: target, + parent: root, + fractional_index: position.position.clone(), + index, + children: self.get_all_hierarchy_nodes_under(TreeParentId::Node(target)), + }) + } + ans + } + fn bfs_all_nodes_for_fast_snapshot(&self) -> Vec { let mut ans = vec![]; self._bfs_all_nodes(TreeParentId::Root, &mut ans); @@ -771,7 +794,7 @@ impl TreeState { ans.push(TreeNode { id: *target, parent: root, - position: position.position.clone(), + fractional_index: position.position.clone(), index, last_move_op: self.trees.get(target).map(|x| x.last_move_op).unwrap(), }); @@ -1210,7 +1233,7 @@ impl ContainerState for TreeState { } fn get_value(&mut self) -> LoroValue { - self.tree_nodes() + self.get_all_hierarchy_nodes_under(TreeParentId::Root) .into_iter() .map(|x| x.into_value()) .collect::>() @@ -1291,26 +1314,36 @@ impl ContainerState for TreeState { } // convert map container to LoroValue -#[allow(clippy::ptr_arg)] pub(crate) fn get_meta_value(nodes: &mut Vec, state: &mut DocState) { for node in nodes.iter_mut() { let map = Arc::make_mut(node.as_map_mut().unwrap()); let meta = map.get_mut("meta").unwrap(); let id = meta.as_container().unwrap(); *meta = state.get_container_deep_value(state.arena.register_container(id)); + let children = map.get_mut("children").unwrap().as_list_mut().unwrap(); + get_meta_value(Arc::make_mut(children), state); } } -#[derive(Debug)] -pub(crate) struct TreeNode { - pub(crate) id: TreeID, - pub(crate) parent: TreeParentId, - pub(crate) position: FractionalIndex, - pub(crate) index: usize, - pub(crate) last_move_op: IdFull, +#[derive(Debug, Clone)] +pub struct TreeNode { + pub id: TreeID, + pub parent: TreeParentId, + pub fractional_index: FractionalIndex, + pub index: usize, + pub last_move_op: IdFull, } -impl TreeNode { +#[derive(Debug, Clone)] +pub struct TreeNodeWithChildren { + pub id: TreeID, + pub parent: TreeParentId, + pub fractional_index: FractionalIndex, + pub index: usize, + pub children: Vec, +} + +impl TreeNodeWithChildren { fn into_value(self) -> LoroValue { let mut t = FxHashMap::default(); t.insert("id".to_string(), self.id.to_string().into()); @@ -1327,7 +1360,15 @@ impl TreeNode { t.insert("index".to_string(), (self.index as i64).into()); t.insert( "fractional_index".to_string(), - self.position.to_string().into(), + self.fractional_index.to_string().into(), + ); + t.insert( + "children".to_string(), + self.children + .into_iter() + .map(|x| x.into_value()) + .collect::>() + .into(), ); t.into() } @@ -1477,7 +1518,7 @@ mod snapshot { let mut position_register = ValueRegister::new(); let mut id_to_idx = FxHashMap::default(); for node in input.iter() { - position_set.insert(node.position.clone()); + position_set.insert(node.fractional_index.clone()); let idx = node_ids.len(); node_ids.push(EncodedTreeNodeId { peer_idx: peers.register(&node.id.peer), @@ -1503,7 +1544,7 @@ mod snapshot { last_set_peer_idx: peers.register(&last_set_id.peer), last_set_counter: last_set_id.counter, last_set_lamport_sub_counter: last_set_id.lamport as i32 - last_set_id.counter, - fractional_index_idx: position_register.register(&node.position), + fractional_index_idx: position_register.register(&node.fractional_index), }) } diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index 470b509b..172bc470 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -24,12 +24,12 @@ use loro_internal::{ undo::{UndoItemMeta, UndoOrRedo}, version::Frontiers, ContainerType, DiffEvent, FxHashMap, HandlerTrait, LoroDoc as LoroDocInner, LoroValue, - MovableListHandler, TreeParentId, UndoManager as InnerUndoManager, + MovableListHandler, TreeNodeWithChildren, TreeParentId, UndoManager as InnerUndoManager, VersionVector as InternalVersionVector, }; use rle::HasLength; use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, cmp::Ordering, rc::Rc, sync::Arc}; +use std::{cell::RefCell, cmp::Ordering, option, rc::Rc, sync::Arc}; use wasm_bindgen::{__rt::IntoJsResult, prelude::*, throw_val}; use wasm_bindgen_derive::TryFromJsValue; @@ -102,6 +102,8 @@ extern "C" { pub type JsTreeID; #[wasm_bindgen(typescript_type = "TreeID | undefined")] pub type JsParentTreeID; + #[wasm_bindgen(typescript_type = "{ withDelted: boolean }")] + pub type JsGetNodesProp; #[wasm_bindgen(typescript_type = "LoroTreeNode | undefined")] pub type JsTreeNodeOrUndefined; #[wasm_bindgen(typescript_type = "string | undefined")] @@ -3433,33 +3435,56 @@ impl LoroTree { Ok(ans) } - /// Get the flat array of the forest. + /// Get the hierarchy array of the forest. /// /// Note: the metadata will be not resolved. So if you don't only care about hierarchy /// but also the metadata, you should use `toJson()`. /// // TODO: perf #[wasm_bindgen(js_name = "toArray", skip_typescript)] - pub fn to_array(&mut self) -> JsResult { - let value = self.handler.get_value().into_list().unwrap(); + pub fn to_array(&self) -> JsResult { + let value = self + .handler + .get_all_hierarchy_nodes_under(TreeParentId::Root); + self.get_node_with_children(value) + } + + /// Get the flat array of the forest. If `with_deleted` is true, the deleted nodes will be included. + #[wasm_bindgen(js_name = "getNodes", skip_typescript)] + pub fn get_nodes(&self, options: JsGetNodesProp) -> JsResult { + let with_deleted = if options.is_undefined() { + false + } else { + Reflect::get(&options.into(), &JsValue::from_str("withDeleted"))? + .as_bool() + .unwrap_or(false) + }; + let nodes = Array::new(); + for v in self.handler.get_nodes_under(TreeParentId::Root) { + let node = LoroTreeNode::from_tree(v.id, self.handler.clone(), self.doc.clone()); + nodes.push(&node.into()); + } + if with_deleted { + for v in self.handler.get_nodes_under(TreeParentId::Deleted) { + let node = LoroTreeNode::from_tree(v.id, self.handler.clone(), self.doc.clone()); + nodes.push(&node.into()); + } + } + Ok(nodes) + } + + fn get_node_with_children(&self, value: Vec) -> JsResult { let ans = Array::new(); - for v in value.as_ref() { - let v = v.as_map().unwrap(); - let id: JsValue = TreeID::try_from(v["id"].as_string().unwrap().as_str()) - .unwrap() - .into(); + for v in value { + let id: JsValue = v.id.into(); let id: JsTreeID = id.into(); - let parent = if let LoroValue::String(p) = &v["parent"] { - Some(TreeID::try_from(p.as_str())?) - } else { - None - }; + let parent = v.parent.tree_id(); let parent: JsParentTreeID = parent .map(|x| LoroTreeNode::from_tree(x, self.handler.clone(), self.doc.clone()).into()) .unwrap_or(JsValue::undefined()) .into(); - let index = *v["index"].as_i64().unwrap() as u32; - let position = v["fractional_index"].as_string().unwrap(); + let index = v.index; + let position = v.fractional_index.to_string(); let map: LoroMap = self.get_node_by_id(&id).unwrap().data()?; let obj = Object::new(); js_sys::Reflect::set(&obj, &"id".into(), &id)?; @@ -3468,15 +3493,17 @@ impl LoroTree { js_sys::Reflect::set( &obj, &"fractional_index".into(), - &JsValue::from_str(position), + &JsValue::from_str(&position), )?; js_sys::Reflect::set(&obj, &"meta".into(), &map.into())?; + let children = self.get_node_with_children(v.children)?; + js_sys::Reflect::set(&obj, &"children".into(), &children)?; ans.push(&obj); } Ok(ans) } - /// Get the flat array with metadata of the forest. + /// Get the hierarchy array with metadata of the forest. /// /// @example /// ```ts @@ -3486,7 +3513,7 @@ impl LoroTree { /// const tree = doc.getTree("tree"); /// const root = tree.createNode(); /// root.data.set("color", "red"); - /// // [ { id: '0@F2462C4159C4C8D1', parent: null, meta: { color: 'red' } } ] + /// // [ { id: '0@F2462C4159C4C8D1', parent: null, meta: { color: 'red' }, children: [] } ] /// console.log(tree.toJSON()); /// ``` #[wasm_bindgen(js_name = "toJSON")] @@ -4530,10 +4557,12 @@ export type TreeNodeValue = { index: number, fractionalIndex: string, meta: LoroMap, + children: TreeNodeValue[], } interface LoroTree{ toArray(): TreeNodeValue[]; + getNodes(options?: { withDeleted: boolean = false }): LoroTreeNode[]; } interface LoroMovableList { diff --git a/crates/loro/src/lib.rs b/crates/loro/src/lib.rs index ba14fa1c..bfc7895c 100644 --- a/crates/loro/src/lib.rs +++ b/crates/loro/src/lib.rs @@ -17,8 +17,10 @@ use loro_internal::obs::LocalUpdateCallback; use loro_internal::undo::{OnPop, OnPush}; use loro_internal::version::ImVersionVector; use loro_internal::DocState; +use loro_internal::FractionalIndex; use loro_internal::LoroDoc as InnerLoroDoc; use loro_internal::OpLog; +use loro_internal::TreeNode as TreeNodeWithId; use loro_internal::TreeParentId; use loro_internal::{ handler::Handler as InnerHandler, ListHandler as InnerListHandler, @@ -1481,6 +1483,14 @@ impl ContainerTrait for LoroTree { } } +#[derive(Debug, Clone)] +pub struct TreeNode { + pub id: TreeID, + pub parent: TreeParentId, + pub fractional_index: FractionalIndex, + pub index: usize, +} + impl LoroTree { /// Create a new container that is detached from the document. /// @@ -1711,6 +1721,22 @@ impl LoroTree { self.handler.nodes() } + /// Return all nodes, if `with_deleted` is true, the deleted nodes will be included. + pub fn get_nodes(&self, with_deleted: bool) -> Vec { + let mut ans = self.handler.get_nodes_under(TreeParentId::Root); + if with_deleted { + ans.extend(self.handler.get_nodes_under(TreeParentId::Deleted)); + } + ans.into_iter() + .map(|x| TreeNode { + id: x.id, + parent: x.parent, + fractional_index: x.fractional_index, + index: x.index, + }) + .collect() + } + /// Return all children of the target node. /// /// If the parent node does not exist, return `None`. diff --git a/crates/loro/tests/loro_rust_test.rs b/crates/loro/tests/loro_rust_test.rs index ef3321e0..91334475 100644 --- a/crates/loro/tests/loro_rust_test.rs +++ b/crates/loro/tests/loro_rust_test.rs @@ -402,7 +402,7 @@ fn tree() { root_meta.insert("color", "red").unwrap(); assert_eq!( tree.get_value_with_meta().to_json(), - r#"[{"parent":null,"meta":{"color":"red"},"id":"0@1","index":0,"fractional_index":"80"},{"parent":"0@1","meta":{},"id":"1@1","index":0,"fractional_index":"80"}]"# + r#"[{"parent":null,"meta":{"color":"red"},"id":"0@1","index":0,"children":[{"parent":"0@1","meta":{},"id":"1@1","index":0,"children":[],"fractional_index":"80"}],"fractional_index":"80"}]"# ) } @@ -1332,22 +1332,23 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> { "meta":{}, "id": "0@0", "index": 0, + "children": [{ + "parent": "0@0", + "meta":{}, + "id": "1@0", + "index": 0, + "children": [], + "fractional_index": "80", + },{ + "parent": "0@0", + "meta":{}, + "id": "3@0", + "index": 1, + "children": [], + "fractional_index": "8180", + },], "fractional_index": "80", }, - { - "parent": "0@0", - "meta":{}, - "id": "1@0", - "index": 0, - "fractional_index": "80", - }, - { - "parent": "0@0", - "meta":{}, - "id": "3@0", - "index": 1, - "fractional_index": "8180", - }, ] }) ); @@ -1361,15 +1362,17 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> { "meta":{}, "id": "0@0", "index": 0, + "children":[{ + "parent": "0@0", + "meta":{}, + "id": "1@0", + "index": 0, + "children": [], + "fractional_index": "80", + }], "fractional_index": "80", }, - { - "parent": "0@0", - "meta":{}, - "id": "1@0", - "index": 0, - "fractional_index": "80", - }, + ] }) ); @@ -1383,6 +1386,7 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> { "meta":{}, "id": "0@0", "index": 0, + "children": [], "fractional_index": "80", }, { @@ -1390,6 +1394,7 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> { "meta":{}, "id": "1@0", "index": 1, + "children": [], "fractional_index": "8180", }, ] @@ -1405,22 +1410,27 @@ fn test_tree_checkout_on_trimmed_doc() -> LoroResult<()> { "meta":{}, "id": "0@0", "index": 0, + "children": [ + { + "parent": "0@0", + "meta":{}, + "id": "1@0", + "index": 0, + "children": [], + "fractional_index": "80", + }, + { + "parent": "0@0", + "meta":{}, + "id": "3@0", + "index": 1, + "children": [], + "fractional_index": "8180", + }, + ], "fractional_index": "80", }, - { - "parent": "0@0", - "meta":{}, - "id": "1@0", - "index": 0, - "fractional_index": "80", - }, - { - "parent": "0@0", - "meta":{}, - "id": "3@0", - "index": 1, - "fractional_index": "8180", - }, + ] }) ); @@ -1473,22 +1483,25 @@ fn test_tree_with_other_ops_checkout_on_trimmed_doc() -> LoroResult<()> { "meta":{}, "id": "0@0", "index": 0, + "children": [{ + "parent": "0@0", + "meta":{}, + "id": "1@0", + "index": 0, + "children": [], + "fractional_index": "80", + }, + { + "parent": "0@0", + "meta":{}, + "id": "3@0", + "index": 1, + "children": [], + "fractional_index": "8180", + },], "fractional_index": "80", }, - { - "parent": "0@0", - "meta":{}, - "id": "1@0", - "index": 0, - "fractional_index": "80", - }, - { - "parent": "0@0", - "meta":{}, - "id": "3@0", - "index": 1, - "fractional_index": "8180", - }, + ] } ) diff --git a/loro-js/tests/tree.test.ts b/loro-js/tests/tree.test.ts index 8e9720b2..f30a114a 100644 --- a/loro-js/tests/tree.test.ts +++ b/loro-js/tests/tree.test.ts @@ -87,13 +87,29 @@ describe("loro tree", () => { tree2.createNode(root.id); tree2.createNode(root.id); const arr = tree2.toArray(); - assertEquals(arr.length, 3); + assertEquals(arr.length, 1); + assertEquals(arr[0].children.length, 2) const keys = Object.keys(arr[0]); assert(keys.includes("id")); assert(keys.includes("parent")); assert(keys.includes("index")); assert(keys.includes("fractional_index")); assert(keys.includes("meta")); + assert(keys.includes("children")); + }); + + it("getNodes", ()=>{ + const loro2 = new LoroDoc(); + const tree2 = loro2.getTree("root"); + const root = tree2.createNode(); + const child = root.createNode(); + const nodes = tree2.getNodes({withDeleted: false}); + assertEquals(nodes.length, 2); + assertEquals(nodes.map((n)=>{return n.id}), [root.id, child.id]) + tree2.delete(child.id); + const nodesWithDeleted = tree2.getNodes({withDeleted:true}); + assertEquals(nodesWithDeleted.map((n)=>{return n.id}), [root.id, child.id]); + assertEquals(tree2.getNodes({withDeleted: false}).map((n)=>{return n.id}), [root.id]); }); it("subscribe", async () => {