refactor: use hierarchy value for tree value (#470)

* refactor: use hierarchy value for tree value

* fix: get_container_deep_value_with_id

* feat: get tree nodes by flat format
This commit is contained in:
Leon Zhao 2024-09-23 09:05:12 +08:00 committed by GitHub
parent bb494df230
commit 3fe619bc81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 310 additions and 169 deletions

View file

@ -479,64 +479,41 @@ struct Node {
position: String,
}
struct FlatNode {
id: String,
parent: Option<String>,
meta: FxHashMap<String, LoroValue>,
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<Self> {
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() {

View file

@ -488,18 +488,9 @@ impl ApplyDiff for TreeTracker {
fn to_value(&self) -> LoroValue {
let mut list: Vec<FxHashMap<_, _>> = 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::<Vec<_>>()
.into(),
);
map
}
}

View file

@ -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::<Vec<_>>(),
doc1.get_tree("tree")
.nodes()
.into_iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
);
{

View file

@ -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();

View file

@ -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<TreeNode> {
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<TreeID> {
self.children(&TreeParentId::Root).unwrap_or_default()
}
pub fn get_all_hierarchy_nodes_under(&self, parent: TreeParentId) -> Vec<TreeNodeWithChildren> {
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 {

View file

@ -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,
})

View file

@ -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;

View file

@ -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)

View file

@ -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<TreeNode> {
self.get_all_tree_nodes_under(TreeParentId::Root)
}
// Get all flat deleted nodes
#[allow(unused)]
pub(crate) fn deleted_tree_nodes(&self) -> Vec<TreeNode> {
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<TreeNode> {
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<TreeNodeWithChildren> {
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<TreeNode> {
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::<Vec<_>>()
@ -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<LoroValue>, 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<TreeNodeWithChildren>,
}
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::<Vec<_>>()
.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),
})
}

View file

@ -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<Array> {
let value = self.handler.get_value().into_list().unwrap();
pub fn to_array(&self) -> JsResult<Array> {
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<Array> {
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<TreeNodeWithChildren>) -> JsResult<Array> {
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 {

View file

@ -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<TreeNode> {
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`.

View file

@ -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",
},
]
}
)

View file

@ -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 () => {