AppFlowy/shared-lib/lib-ot/tests/node/tree_test.rs
Nathan.fooo f1a5726fcb
Feat: add appflowy editor in backend (#1320)
* chore: remove update attributes

* chore: format code

* chore: extension for transaction

* refactor: add document editor trait

* chore: add appflowy_document editor

* chore: add document serde

* chore: add new document editor

* chore: add tests

* chore: add more test

* chore: add test

Co-authored-by: nathan <nathan@appflowy.io>
2022-10-20 11:35:11 +08:00

801 lines
21 KiB
Rust

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 lib_ot::core::{NodeData, NodeDataBuilder, Path};
use lib_ot::text_delta::{TextOperationBuilder, TextOperations};
#[test]
fn node_insert_test() {
let mut test = NodeTest::new();
let node_data = NodeData::new("text");
let path: Path = vec![0].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]
#[should_panic]
fn node_insert_with_empty_path_test() {
let mut test = NodeTest::new();
let scripts = vec![InsertNode {
path: vec![].into(),
node_data: NodeData::new("text"),
rev_id: 1,
}];
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.clone())
.add_node_data(image_b.clone())
.build();
let text_node = NodeDataBuilder::new("text_1").add_node_data(image.clone()).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();
let node_1 = NodeData::new("a");
let node_2 = NodeData::new("b");
let node_3 = NodeData::new("c");
let node_data_list = vec![node_1, node_2, node_3];
let path: Path = vec![0].into();
// Insert three nodes under the root
let scripts = vec![
// 0:a
// 1:b
// 2:c
InsertNodes {
path,
node_data_list: node_data_list.clone(),
rev_id: 1,
},
AssertNodesAtRoot {
expected: node_data_list,
},
];
test.run_scripts(scripts);
}
#[test]
fn tree_insert_multiple_nodes_at_root_path_test2() {
let mut test = NodeTest::new();
let node_1 = NodeData::new("a");
let node_2 = NodeData::new("b");
let node_3 = NodeData::new("c");
let scripts = vec![
InsertNode {
path: 0.into(),
node_data: node_1.clone(),
rev_id: 1,
},
InsertNode {
path: 1.into(),
node_data: node_2.clone(),
rev_id: 2,
},
InsertNode {
path: 2.into(),
node_data: node_3.clone(),
rev_id: 3,
},
// 0:a
// 1:b
// 2:c
AssertNode {
path: 0.into(),
expected: Some(node_1),
},
AssertNode {
path: 1.into(),
expected: Some(node_2),
},
AssertNode {
path: 2.into(),
expected: Some(node_3),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_insert_node_with_children_test() {
let mut test = NodeTest::new();
let image_1 = NodeData::new("image_a");
let image_2 = NodeData::new("image_b");
let image = NodeDataBuilder::new("image")
.add_node_data(image_1.clone())
.add_node_data(image_2.clone())
.build();
let node_data = NodeDataBuilder::new("text").add_node_data(image.clone()).build();
let path: Path = 0.into();
let scripts = vec![
InsertNode {
path: path.clone(),
node_data: node_data.clone(),
rev_id: 1,
},
// 0:text
// 0:image
// 0:image_1
// 1:image_2
AssertNode {
path,
expected: Some(node_data),
},
AssertNode {
path: vec![0, 0].into(),
expected: Some(image),
},
AssertNode {
path: vec![0, 0, 0].into(),
expected: Some(image_1),
},
AssertNode {
path: vec![0, 0, 1].into(),
expected: Some(image_2),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_insert_node_in_ordered_nodes_test() {
let mut test = NodeTest::new();
let path_1: Path = 0.into();
let node_1 = NodeData::new("text_1");
let path_2: Path = 1.into();
let node_2_1 = NodeData::new("text_2_1");
let node_2_2 = NodeData::new("text_2_2");
let path_3: Path = 2.into();
let node_3 = NodeData::new("text_3");
let scripts = vec![
InsertNode {
path: path_1.clone(),
node_data: node_1.clone(),
rev_id: 1,
},
InsertNode {
path: path_2.clone(),
node_data: node_2_1.clone(),
rev_id: 2,
},
InsertNode {
path: path_3.clone(),
node_data: node_3,
rev_id: 3,
},
// 0:text_1
// 1:text_2_1
// 2:text_3
InsertNode {
path: path_2.clone(),
node_data: node_2_2.clone(),
rev_id: 4,
},
// 0:text_1
// 1:text_2_2
// 2:text_2_1
// 3:text_3
AssertNode {
path: path_1,
expected: Some(node_1),
},
AssertNode {
path: path_2,
expected: Some(node_2_2),
},
AssertNode {
path: path_3,
expected: Some(node_2_1),
},
AssertNumberOfChildrenAtPath {
path: None,
expected: 4,
},
];
test.run_scripts(scripts);
}
#[test]
fn node_insert_nested_nodes_test() {
let mut test = NodeTest::new();
let node_data_1_1 = NodeDataBuilder::new("text_1_1").build();
let node_data_1_2 = NodeDataBuilder::new("text_1_2").build();
let node_data_1 = NodeDataBuilder::new("text_1")
.add_node_data(node_data_1_1.clone())
.add_node_data(node_data_1_2.clone())
.build();
let node_data_2_1 = NodeDataBuilder::new("text_2_1").build();
let node_data_2_2 = NodeDataBuilder::new("text_2_2").build();
let node_data_2 = NodeDataBuilder::new("text_2")
.add_node_data(node_data_2_1.clone())
.add_node_data(node_data_2_2.clone())
.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,
},
// the tree will be:
// 0:text_1
// 0:text_1_1
// 1:text_1_2
// 1:text_2
// 0:text_2_1
// 1:text_2_2
AssertNode {
path: vec![0, 0].into(),
expected: Some(node_data_1_1.into()),
},
AssertNode {
path: vec![0, 1].into(),
expected: Some(node_data_1_2.into()),
},
AssertNode {
path: vec![1, 0].into(),
expected: Some(node_data_2_1.into()),
},
AssertNode {
path: vec![1, 1].into(),
expected: Some(node_data_2_2.into()),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_insert_node_before_existing_nested_nodes_test() {
let mut test = NodeTest::new();
let node_data_1_1 = NodeDataBuilder::new("text_1_1").build();
let node_data_1_2 = NodeDataBuilder::new("text_1_2").build();
let node_data_1 = NodeDataBuilder::new("text_1")
.add_node_data(node_data_1_1.clone())
.add_node_data(node_data_1_2.clone())
.build();
let scripts = vec![
InsertNode {
path: 0.into(),
node_data: node_data_1,
rev_id: 1,
},
// 0:text_1
// 0:text_1_1
// 1:text_1_2
InsertNode {
path: 0.into(),
node_data: NodeDataBuilder::new("text_0").build(),
rev_id: 2,
},
// 0:text_0
// 1:text_1
// 0:text_1_1
// 1:text_1_2
AssertNode {
path: vec![1, 0].into(),
expected: Some(node_data_1_1.into()),
},
AssertNode {
path: vec![1, 1].into(),
expected: Some(node_data_1_2.into()),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_insert_with_attributes_test() {
let mut test = NodeTest::new();
let path: Path = 0.into();
let mut inserted_node = NodeData::new("text");
inserted_node.attributes.insert("bold", true);
inserted_node.attributes.insert("underline", true);
let scripts = vec![
InsertNode {
path: path.clone(),
node_data: inserted_node.clone(),
rev_id: 1,
},
UpdateAttributes {
path: path.clone(),
attributes: inserted_node.attributes.clone(),
},
AssertNode {
path,
expected: Some(inserted_node),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_delete_test() {
let mut test = NodeTest::new();
let inserted_node = NodeData::new("text");
let path: Path = 0.into();
let scripts = vec![
InsertNode {
path: path.clone(),
node_data: inserted_node,
rev_id: 1,
},
DeleteNode {
path: path.clone(),
rev_id: 2,
},
AssertNode { path, expected: None },
];
test.run_scripts(scripts);
}
#[test]
fn node_delete_node_from_list_test() {
let mut test = NodeTest::new();
let image_a = NodeData::new("image_a");
let image_b = NodeData::new("image_b");
let image_1 = NodeDataBuilder::new("image_1")
.add_node_data(image_a.clone())
.add_node_data(image_b.clone())
.build();
let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1.clone()).build();
let image_2 = NodeDataBuilder::new("image_2")
.add_node_data(image_a.clone())
.add_node_data(image_b.clone())
.build();
let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2.clone()).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.clone(),
rev_id: 1,
},
DeleteNode {
path: 0.into(),
rev_id: 2,
},
AssertNode {
path: 1.into(),
expected: None,
},
AssertNode {
path: 0.into(),
expected: Some(text_node_2),
},
AssertNode {
path: vec![0, 0].into(),
expected: Some(image_2),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_delete_nested_node_test() {
let mut test = NodeTest::new();
let image_a = NodeData::new("image_a");
let image_b = NodeData::new("image_b");
let image_1 = NodeDataBuilder::new("image_1")
.add_node_data(image_a.clone())
.add_node_data(image_b.clone())
.build();
let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1.clone()).build();
let image_2 = NodeDataBuilder::new("image_2")
.add_node_data(image_a.clone())
.add_node_data(image_b.clone())
.build();
let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2.clone()).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.clone(),
rev_id: 1,
},
// 0:text_1
// 0:image_1
// 0:image_a
// 1:image_b
// 1:text_2
// 0:image_2
// 0:image_a
// 1:image_b
DeleteNode {
path: vec![0, 0, 0].into(),
rev_id: 2,
},
// 0:text_1
// 0:image_1
// 0:image_b
// 1:text_2
// 0:image_2
// 0:image_a
// 1:image_b
AssertNode {
path: vec![0, 0, 0].into(),
expected: Some(image_b.clone()),
},
DeleteNode {
path: vec![0, 0].into(),
rev_id: 3,
},
// 0:text_1
// 1:text_2
// 0:image_2
// 0:image_a
// 1:image_b
AssertNumberOfChildrenAtPath {
path: Some(0.into()),
expected: 0,
},
AssertNode {
path: vec![0].into(),
expected: Some(NodeDataBuilder::new("text_1").build()),
},
AssertNode {
path: vec![1, 0, 0].into(),
expected: Some(image_a.clone()),
},
AssertNode {
path: vec![1, 0, 1].into(),
expected: Some(image_b.clone()),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_delete_children_test() {
let mut test = NodeTest::new();
let inserted_node = NodeDataBuilder::new("text")
.add_node_data(NodeDataBuilder::new("sub_text_1").build())
.add_node_data(NodeDataBuilder::new("sub_text_2").build())
.add_node_data(NodeDataBuilder::new("sub_text_3").build())
.build();
let scripts = vec![
InsertNode {
path: vec![0].into(),
node_data: inserted_node,
rev_id: 1,
},
AssertNode {
path: vec![0, 0].into(),
expected: Some(NodeDataBuilder::new("sub_text_1").build()),
},
AssertNode {
path: vec![0, 1].into(),
expected: Some(NodeDataBuilder::new("sub_text_2").build()),
},
AssertNode {
path: vec![0, 2].into(),
expected: Some(NodeDataBuilder::new("sub_text_3").build()),
},
AssertNumberOfChildrenAtPath {
path: Some(Path(vec![0])),
expected: 3,
},
DeleteNode {
path: vec![0, 0].into(),
rev_id: 2,
},
AssertNode {
path: vec![0, 0].into(),
expected: Some(NodeDataBuilder::new("sub_text_2").build()),
},
AssertNumberOfChildrenAtPath {
path: Some(Path(vec![0])),
expected: 2,
},
];
test.run_scripts(scripts);
}
#[test]
fn node_reorder_sub_nodes_test() {
let mut test = NodeTest::new();
let image_a = NodeData::new("image_a");
let image_b = NodeData::new("image_b");
let child_1 = NodeDataBuilder::new("image_1")
.add_node_data(image_a.clone())
.add_node_data(image_b.clone())
.build();
let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(child_1.clone()).build();
let scripts = vec![
InsertNode {
path: 0.into(),
node_data: text_node_1,
rev_id: 1,
},
// 0:text_1
// 0:image_1
// 0:image_a
// 1:image_b
DeleteNode {
path: vec![0, 0, 0].into(),
rev_id: 2,
},
// 0:text_1
// 0:image_1
// 0:image_b
InsertNode {
path: vec![0, 0, 1].into(),
node_data: image_a.clone(),
rev_id: 3,
},
// 0:text_1
// 0:image_1
// 0:image_b
// 1:image_a
AssertNode {
path: vec![0, 0, 0].into(),
expected: Some(image_b.clone()),
},
AssertNode {
path: vec![0, 0, 1].into(),
expected: Some(image_a.clone()),
},
];
test.run_scripts(scripts);
}
#[test]
fn node_reorder_nodes_test() {
let mut test = NodeTest::new();
let image_a = NodeData::new("image_a");
let image_b = NodeData::new("image_b");
let image_1 = NodeDataBuilder::new("image_1")
.add_node_data(image_a.clone())
.add_node_data(image_b.clone())
.build();
let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1.clone()).build();
let image_2 = NodeDataBuilder::new("image_2")
.add_node_data(image_a.clone())
.add_node_data(image_b.clone())
.build();
let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2.clone()).build();
let scripts = vec![
InsertNode {
path: 0.into(),
node_data: text_node_1.clone(),
rev_id: 1,
},
InsertNode {
path: 0.into(),
node_data: text_node_2.clone(),
rev_id: 1,
},
// 0:text_1
// 0:image_1
// 0:image_a
// 1:image_b
// 1:text_2
// 0:image_2
// 0:image_a
// 1:image_b
DeleteNode {
path: vec![0].into(),
rev_id: 2,
},
InsertNode {
path: vec![1].into(),
node_data: text_node_1.clone(),
rev_id: 3,
},
// 0:text_2
// 0:image_2
// 0:image_a
// 1:image_b
// 1:text_1
// 0:image_1
// 0:image_a
// 1:image_b
AssertNode {
path: vec![0].into(),
expected: Some(text_node_2.clone()),
},
AssertNode {
path: vec![0, 0].into(),
expected: Some(image_2.clone()),
},
AssertNode {
path: vec![0, 0, 0].into(),
expected: Some(image_a.clone()),
},
AssertNode {
path: vec![1].into(),
expected: Some(text_node_1.clone()),
},
AssertNode {
path: vec![1, 0].into(),
expected: Some(image_1.clone()),
},
AssertNode {
path: vec![1, 0, 1].into(),
expected: Some(image_b.clone()),
},
];
test.run_scripts(scripts);
}
#[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 scripts = vec![
InsertNode {
path: 0.into(),
node_data: node,
rev_id: 1,
},
UpdateBody {
path: 0.into(),
changeset,
},
AssertNodeDelta {
path: 0.into(),
expected,
},
];
test.run_scripts(scripts);
}
#[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 scripts = vec![
InsertNode {
path: 0.into(),
node_data: node,
rev_id: 1,
},
UpdateBody {
path: 0.into(),
changeset,
},
UpdateBody {
path: 0.into(),
changeset: inverted_changeset,
},
AssertNodeDelta {
path: 0.into(),
expected: initial_delta,
},
];
test.run_scripts(scripts);
}
fn make_node_delta_changeset(
initial_content: &str,
insert_str: &str,
) -> (TextOperations, Changeset, Changeset, TextOperations) {
let initial_content = initial_content.to_owned();
let initial_delta = TextOperationBuilder::new().insert(&initial_content).build();
let delta = TextOperationBuilder::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)
}