641fd11703
Structs should use `new` for the constructor name and implement `Default` where they can.
299 lines
11 KiB
Rust
299 lines
11 KiB
Rust
use std::convert::TryInto;
|
|
|
|
use automerge_backend::Backend;
|
|
use automerge_frontend::{Frontend, InvalidChangeRequest, LocalChange, Path, Primitive, Value};
|
|
use automerge_protocol as amp;
|
|
use maplit::hashmap;
|
|
use unicode_segmentation::UnicodeSegmentation;
|
|
|
|
#[test]
|
|
fn test_allow_cursor_on_list_element() {
|
|
let _ = env_logger::builder().is_test(true).try_init().unwrap();
|
|
let mut frontend = Frontend::new();
|
|
let change = frontend
|
|
.change::<_, _, InvalidChangeRequest>(None, |d| {
|
|
d.add_change(LocalChange::set(Path::root().key("list"), vec![1, 2, 3]))?;
|
|
let cursor = d
|
|
.cursor_to_path(&Path::root().key("list").index(1))
|
|
.unwrap();
|
|
d.add_change(LocalChange::set(Path::root().key("cursor"), cursor))?;
|
|
Ok(())
|
|
})
|
|
.unwrap()
|
|
.1
|
|
.unwrap();
|
|
let mut backend = Backend::new();
|
|
backend
|
|
.apply_changes(vec![change.try_into().unwrap()])
|
|
.unwrap();
|
|
|
|
let mut backend2 = Backend::new();
|
|
backend2
|
|
.apply_changes(backend.get_changes(&[]).into_iter().cloned().collect())
|
|
.unwrap();
|
|
let mut frontend2 = Frontend::new();
|
|
frontend2
|
|
.apply_patch(backend2.get_patch().unwrap())
|
|
.unwrap();
|
|
let index_value = frontend2
|
|
.value_at_path(&Path::root().key("cursor"))
|
|
.unwrap();
|
|
if let Value::Primitive(Primitive::Cursor(c)) = index_value {
|
|
assert_eq!(c.index, 1)
|
|
} else {
|
|
panic!("value was not a cursor");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_allow_cursor_on_text_element() {
|
|
let mut frontend = Frontend::new();
|
|
let change = frontend
|
|
.change::<_, _, InvalidChangeRequest>(None, |d| {
|
|
d.add_change(LocalChange::set(
|
|
Path::root().key("list"),
|
|
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
|
|
))?;
|
|
let cursor = d
|
|
.cursor_to_path(&Path::root().key("list").index(1))
|
|
.unwrap();
|
|
d.add_change(LocalChange::set(Path::root().key("cursor"), cursor))?;
|
|
Ok(())
|
|
})
|
|
.unwrap()
|
|
.1
|
|
.unwrap();
|
|
let mut backend = Backend::new();
|
|
backend
|
|
.apply_changes(vec![change.try_into().unwrap()])
|
|
.unwrap();
|
|
|
|
let mut backend2 = Backend::new();
|
|
backend2
|
|
.apply_changes(backend.get_changes(&[]).into_iter().cloned().collect())
|
|
.unwrap();
|
|
let mut frontend2 = Frontend::new();
|
|
frontend2
|
|
.apply_patch(backend2.get_patch().unwrap())
|
|
.unwrap();
|
|
let index_value = frontend2
|
|
.value_at_path(&Path::root().key("cursor"))
|
|
.unwrap();
|
|
if let Value::Primitive(Primitive::Cursor(c)) = index_value {
|
|
assert_eq!(c.index, 1)
|
|
} else {
|
|
panic!("value was not a cursor");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_do_not_allow_index_past_end_of_list() {
|
|
let mut frontend = Frontend::new();
|
|
frontend
|
|
.change::<_, _, InvalidChangeRequest>(None, |d| {
|
|
d.add_change(LocalChange::set(
|
|
Path::root().key("list"),
|
|
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
|
|
))?;
|
|
let cursor = d.cursor_to_path(&Path::root().key("list").index(10));
|
|
assert_eq!(cursor, None);
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_updates_cursor_during_change_function() {
|
|
let mut frontend = Frontend::new();
|
|
frontend
|
|
.change::<_, _, InvalidChangeRequest>(None, |d| {
|
|
d.add_change(LocalChange::set(
|
|
Path::root().key("list"),
|
|
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
|
|
))?;
|
|
let cursor = d
|
|
.cursor_to_path(&Path::root().key("list").index(1))
|
|
.unwrap();
|
|
d.add_change(LocalChange::set(Path::root().key("cursor"), cursor))?;
|
|
let cursor_the_second = d.value_at_path(&Path::root().key("cursor"));
|
|
if let Some(Value::Primitive(Primitive::Cursor(c))) = cursor_the_second {
|
|
assert_eq!(c.index, 1);
|
|
} else {
|
|
panic!("Cursor the second not found");
|
|
}
|
|
|
|
d.add_change(LocalChange::insert(
|
|
Path::root().key("list").index(0),
|
|
Value::Primitive(Primitive::Str("0".to_string())),
|
|
))?;
|
|
let cursor_the_third = d.value_at_path(&Path::root().key("cursor"));
|
|
if let Some(Value::Primitive(Primitive::Cursor(c))) = cursor_the_third {
|
|
assert_eq!(c.index, 2);
|
|
} else {
|
|
panic!("Cursor the third not found");
|
|
}
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_cursor_to_new_element_in_diff() {
|
|
let mut frontend = Frontend::new();
|
|
let actor = frontend.actor_id.clone();
|
|
let patch1 = amp::Patch {
|
|
actor: Some(actor.clone()),
|
|
deps: Vec::new(),
|
|
seq: Some(1),
|
|
clock: hashmap! {actor.clone() => 1},
|
|
max_op: 3,
|
|
pending_changes: 0,
|
|
diffs: Some(amp::Diff::Map(amp::MapDiff {
|
|
obj_type: amp::MapType::Map,
|
|
object_id: amp::ObjectId::Root,
|
|
props: hashmap! {
|
|
"list".to_string() => hashmap!{
|
|
actor.op_id_at(1) => amp::Diff::Seq(amp::SeqDiff{
|
|
object_id: actor.op_id_at(1).into(),
|
|
obj_type: amp::SequenceType::List,
|
|
edits: vec![
|
|
amp::DiffEdit::Insert{index: 0, elem_id: actor.op_id_at(2).into()},
|
|
amp::DiffEdit::Insert{index: 1, elem_id: actor.op_id_at(3).into()},
|
|
],
|
|
props: hashmap!{
|
|
0 => hashmap!{
|
|
actor.op_id_at(2) => amp::Diff::Value("one".into())
|
|
},
|
|
1 => hashmap!{
|
|
actor.op_id_at(3) => amp::Diff::Value("two".into())
|
|
}
|
|
}
|
|
}),
|
|
},
|
|
"cursor".to_string() => hashmap!{
|
|
actor.op_id_at(4) => amp::Diff::Cursor(amp::CursorDiff{
|
|
elem_id: actor.op_id_at(3),
|
|
index: 1,
|
|
object_id: actor.op_id_at(1).into(),
|
|
})
|
|
},
|
|
},
|
|
})),
|
|
};
|
|
|
|
frontend.apply_patch(patch1).unwrap();
|
|
let patch2 = amp::Patch {
|
|
actor: Some(actor.clone()),
|
|
deps: Vec::new(),
|
|
seq: Some(2),
|
|
clock: hashmap! {actor.clone() => 2},
|
|
max_op: 5,
|
|
pending_changes: 0,
|
|
diffs: Some(amp::Diff::Map(amp::MapDiff {
|
|
object_id: amp::ObjectId::Root,
|
|
obj_type: amp::MapType::Map,
|
|
props: hashmap! {
|
|
"cursor".to_string() => hashmap!{
|
|
actor.op_id_at(4) => amp::Diff::Cursor(amp::CursorDiff{
|
|
elem_id: actor.op_id_at(2),
|
|
index: 0,
|
|
object_id: actor.op_id_at(1).into(),
|
|
})
|
|
}
|
|
},
|
|
})),
|
|
};
|
|
frontend.apply_patch(patch2).unwrap();
|
|
|
|
frontend
|
|
.change::<_, _, InvalidChangeRequest>(None, |doc| {
|
|
doc.add_change(LocalChange::insert(
|
|
Path::root().key("list").index(1),
|
|
"three".into(),
|
|
))?;
|
|
let cursor = doc.value_at_path(&Path::root().key("cursor")).unwrap();
|
|
match cursor {
|
|
Value::Primitive(Primitive::Cursor(c)) => assert_eq!(c.index, 0),
|
|
_ => panic!("Cursor value was not a cursor"),
|
|
}
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_cursor_to_new_element_in_local_change() {
|
|
let mut frontend = Frontend::new();
|
|
frontend
|
|
.change::<_, _, InvalidChangeRequest>(None, |d| {
|
|
d.add_change(LocalChange::set(
|
|
Path::root().key("list"),
|
|
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
|
|
))?;
|
|
let cursor = d
|
|
.cursor_to_path(&Path::root().key("list").index(1))
|
|
.unwrap();
|
|
d.add_change(LocalChange::set(Path::root().key("cursor"), cursor))?;
|
|
let cursor_the_second = d.value_at_path(&Path::root().key("cursor"));
|
|
if let Some(Value::Primitive(Primitive::Cursor(c))) = cursor_the_second {
|
|
assert_eq!(c.index, 1);
|
|
} else {
|
|
panic!("Cursor the second not found");
|
|
}
|
|
|
|
d.add_change(LocalChange::insert(
|
|
Path::root().key("list").index(0),
|
|
Value::Primitive(Primitive::Str("0".to_string())),
|
|
))?;
|
|
d.add_change(LocalChange::insert(
|
|
Path::root().key("list").index(0),
|
|
Value::Primitive(Primitive::Str("1".to_string())),
|
|
))?;
|
|
d.add_change(LocalChange::set(
|
|
Path::root().key("cursor"),
|
|
d.cursor_to_path(&Path::root().key("list").index(2))
|
|
.unwrap(),
|
|
))?;
|
|
d.add_change(LocalChange::insert(
|
|
Path::root().key("list").index(4),
|
|
"2".into(),
|
|
))?;
|
|
let cursor_the_third = d.value_at_path(&Path::root().key("cursor"));
|
|
if let Some(Value::Primitive(Primitive::Cursor(c))) = cursor_the_third {
|
|
assert_eq!(c.index, 3);
|
|
} else {
|
|
panic!("Cursor the third not found");
|
|
}
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_delete_cursor_and_adding_again() {
|
|
let mut frontend = Frontend::new();
|
|
frontend
|
|
.change::<_, _, InvalidChangeRequest>(None, |d| {
|
|
d.add_change(LocalChange::set(
|
|
Path::root().key("list"),
|
|
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
|
|
))?;
|
|
let cursor = d
|
|
.cursor_to_path(&Path::root().key("list").index(1))
|
|
.unwrap();
|
|
d.add_change(LocalChange::set(Path::root().key("cursor"), cursor.clone()))?;
|
|
d.add_change(LocalChange::delete(Path::root().key("cursor")))?;
|
|
d.add_change(LocalChange::set(Path::root().key("cursor"), cursor))?;
|
|
|
|
let cursor_value = d.value_at_path(&Path::root().key("cursor"));
|
|
if let Some(Value::Primitive(Primitive::Cursor(c))) = cursor_value {
|
|
assert_eq!(c.index, 1);
|
|
} else {
|
|
panic!("Cursor the third not found");
|
|
}
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
//TODO test removing a cursors
|