automerge/automerge-frontend/tests/test_apply_patch.rs
2021-04-26 09:42:36 -07:00

979 lines
33 KiB
Rust

use std::convert::TryInto;
use automerge_frontend::{Frontend, Path, Primitive, Value};
use automerge_protocol as amp;
use maplit::hashmap;
use unicode_segmentation::UnicodeSegmentation;
#[test]
fn set_object_root_properties() {
let actor = amp::ActorId::random();
let patch = amp::Patch {
actor: None,
seq: None,
max_op: 1,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"bird".into() => hashmap!{
actor.op_id_at(1) => "magpie".into()
}
},
})),
};
let mut frontend = Frontend::new();
frontend.apply_patch(patch).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"bird" => "magpie"})
);
}
#[test]
fn reveal_conflicts_on_root_properties() {
// We don't just use random actor IDs because we need to have a specific
// ordering (actor1 > actor2)
let actor1 = amp::ActorId::from_bytes(
uuid::Uuid::parse_str("02ef21f3-c9eb-4087-880e-bedd7c4bbe43")
.unwrap()
.as_bytes(),
);
let actor2 = amp::ActorId::from_bytes(
uuid::Uuid::parse_str("2a1d376b-24f7-4400-8d4a-f58252d644dd")
.unwrap()
.as_bytes(),
);
let patch = amp::Patch {
actor: None,
seq: None,
max_op: 2,
pending_changes: 0,
clock: hashmap! {
actor1.clone() => 1,
actor2.clone() => 2,
},
deps: Vec::new(),
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"favouriteBird".into() => hashmap!{
actor1.op_id_at(1) => amp::Diff::Value("robin".into()),
actor2.op_id_at(1) => amp::Diff::Value("wagtail".into()),
}
},
})),
};
let mut doc = Frontend::new();
doc.apply_patch(patch).unwrap();
assert_eq!(
doc.state(),
&Into::<Value>::into(hashmap! {"favouriteBird" => "wagtail"})
);
let conflicts = doc.get_conflicts(&Path::root().key("favouriteBird"));
assert_eq!(
conflicts,
Some(hashmap! {
actor1.op_id_at(1) => "robin".into(),
actor2.op_id_at(1) => "wagtail".into(),
})
)
}
#[test]
fn create_nested_maps() {
let actor = amp::ActorId::random();
let patch = amp::Patch {
actor: None,
seq: None,
max_op: 3,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor.op_id_at(2).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"wrens".into() => hashmap!{
actor.op_id_at(2) => amp::Diff::Value(amp::ScalarValue::Int(3))
}
}
})
}
},
})),
};
let mut frontend = Frontend::new();
frontend.apply_patch(patch).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"birds" => hashmap!{"wrens" => Primitive::Int(3)}})
);
}
#[test]
fn apply_updates_inside_nested_maps() {
let actor = amp::ActorId::random();
let patch1 = amp::Patch {
actor: None,
seq: None,
max_op: 2,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor.op_id_at(2).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"wrens".into() => hashmap!{
actor.op_id_at(2) => amp::Diff::Value(amp::ScalarValue::Int(3))
}
}
})
}
},
})),
};
let mut frontend = Frontend::new();
frontend.apply_patch(patch1).unwrap();
let birds_id = frontend.get_object_id(&Path::root().key("birds")).unwrap();
let patch2 = amp::Patch {
actor: None,
seq: None,
max_op: 3,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 2,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: birds_id,
obj_type: amp::MapType::Map,
props: hashmap!{
"sparrows".into() => hashmap!{
actor.op_id_at(3) => amp::Diff::Value(amp::ScalarValue::Int(15))
}
}
})
}
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"birds" => hashmap!{"wrens" => Primitive::Int(3), "sparrows" => Primitive::Int(15)}}
)
);
}
#[test]
fn apply_updates_inside_map_conflicts() {
// We don't just use random actor IDs because we need to have a specific
// ordering (actor1 < actor2)
let actor1 = amp::ActorId::from_bytes(
uuid::Uuid::parse_str("02ef21f3-c9eb-4087-880e-bedd7c4bbe43")
.unwrap()
.as_bytes(),
);
let actor2 = amp::ActorId::from_bytes(
uuid::Uuid::parse_str("2a1d376b-24f7-4400-8d4a-f58252d644dd")
.unwrap()
.as_bytes(),
);
let patch1 = amp::Patch {
actor: None,
seq: None,
max_op: 2,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor1.clone() => 1,
actor2.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"favouriteBirds".into() => hashmap!{
actor1.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor1.op_id_at(1).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"blackbirds".into() => hashmap!{
actor1.op_id_at(2) => amp::Diff::Value(amp::ScalarValue::Int(1)),
}
},
}),
actor2.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor2.op_id_at(1).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"wrens".into() => hashmap!{
actor2.op_id_at(2) => amp::Diff::Value(amp::ScalarValue::Int(3)),
}
},
})
}
},
})),
};
let mut frontend = Frontend::new();
frontend.apply_patch(patch1).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"favouriteBirds" => hashmap!{"wrens" => Primitive::Int(3)}})
);
assert_eq!(
frontend
.get_conflicts(&Path::root().key("favouriteBirds"))
.unwrap(),
hashmap! {
actor1.op_id_at(1) => hashmap!{"blackbirds" => Primitive::Int(1)}.into(),
actor2.op_id_at(1) => hashmap!{"wrens" => Primitive::Int(3)}.into(),
}
);
let patch2 = amp::Patch {
actor: None,
seq: None,
max_op: 1,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor1.clone() => 2,
actor2.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"favouriteBirds".into() => hashmap!{
actor1.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor1.op_id_at(1).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"blackbirds".into() => hashmap!{
actor1.op_id_at(3) => amp::Diff::Value(amp::ScalarValue::Int(2)),
}
},
}),
actor2.op_id_at(1) => amp::Diff::Unchanged(amp::ObjDiff{
object_id: actor2.op_id_at(1).into(),
obj_type: amp::ObjType::Map(amp::MapType::Map),
})
}
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"favouriteBirds" => hashmap!{"wrens" => Primitive::Int(3)}})
);
assert_eq!(
frontend
.get_conflicts(&Path::root().key("favouriteBirds"))
.unwrap(),
hashmap! {
actor1.op_id_at(1) => hashmap!{"blackbirds" => Primitive::Int(2)}.into(),
actor2.op_id_at(1) => hashmap!{"wrens" => Primitive::Int(3)}.into(),
}
);
}
#[test]
fn delete_keys_in_maps() {
let actor = amp::ActorId::random();
let mut frontend = Frontend::new();
let patch1 = amp::Patch {
actor: None,
max_op: 2,
pending_changes: 0,
seq: None,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"magpies".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Value(amp::ScalarValue::Int(2))
},
"sparrows".into() => hashmap!{
actor.op_id_at(2) => amp::Diff::Value(amp::ScalarValue::Int(15))
}
},
})),
};
frontend.apply_patch(patch1).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"magpies" => Primitive::Int(2), "sparrows" => Primitive::Int(15)}
)
);
let patch2 = amp::Patch {
actor: None,
seq: None,
max_op: 3,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor => 2,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"magpies".into() => hashmap!{}
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"sparrows" => Primitive::Int(15)})
);
}
#[test]
fn create_lists() {
let actor = amp::ActorId::random();
let mut frontend = Frontend::new();
let patch = amp::Patch {
actor: None,
seq: None,
max_op: 2,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 2,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => 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() }],
props: hashmap!{
0 => hashmap!{
actor.op_id_at(2) => amp::Diff::Value("chaffinch".into())
}
}
})
}
},
})),
};
frontend.apply_patch(patch).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"birds" => vec!["chaffinch"]})
)
}
#[test]
fn apply_updates_inside_lists() {
let actor = amp::ActorId::random();
let mut frontend = Frontend::new();
let patch = amp::Patch {
actor: None,
seq: None,
max_op: 1,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => 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() }],
props: hashmap!{
0 => hashmap!{
actor.op_id_at(2) => amp::Diff::Value("chaffinch".into())
}
}
})
}
},
})),
};
frontend.apply_patch(patch).unwrap();
let patch2 = amp::Patch {
actor: None,
seq: None,
max_op: 3,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 2,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => 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![],
props: hashmap!{
0 => hashmap!{
actor.op_id_at(3) => amp::Diff::Value("greenfinch".into())
}
}
})
}
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"birds" => vec!["greenfinch"]})
)
}
#[test]
fn apply_updates_inside_list_conflicts() {
// We don't just use random actor IDs because we need to have a specific
// ordering (actor1 < actor2)
let actor1 = amp::ActorId::from_bytes(
uuid::Uuid::parse_str("02ef21f3-c9eb-4087-880e-bedd7c4bbe43")
.unwrap()
.as_bytes(),
);
let actor2 = amp::ActorId::from_bytes(
uuid::Uuid::parse_str("2a1d376b-24f7-4400-8d4a-f58252d644dd")
.unwrap()
.as_bytes(),
);
let other_actor = amp::ActorId::random();
let patch1 = amp::Patch {
actor: None,
seq: None,
max_op: 2,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
other_actor.clone() => 1,
actor1.clone() => 1,
actor2.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
other_actor.op_id_at(1) => amp::Diff::Seq(amp::SeqDiff{
object_id: other_actor.op_id_at(1).into(),
obj_type: amp::SequenceType::List,
edits: vec![amp::DiffEdit::Insert{ index: 0, elem_id: actor1.op_id_at(2).into()}],
props: hashmap!{
0 => hashmap!{
actor1.op_id_at(2) => amp::Diff::Map(amp::MapDiff{
object_id: actor1.op_id_at(2).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"species".into() => hashmap!{
actor1.op_id_at(3) => amp::Diff::Value("woodpecker".into()),
},
"numSeen".into() => hashmap!{
actor1.op_id_at(4) => amp::Diff::Value(amp::ScalarValue::Int(1)),
},
}
}),
actor2.op_id_at(2) => amp::Diff::Map(amp::MapDiff{
object_id: actor2.op_id_at(2).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"species".into() => hashmap!{
actor2.op_id_at(3) => amp::Diff::Value("lapwing".into()),
},
"numSeen".into() => hashmap!{
actor2.op_id_at(4) => amp::Diff::Value(amp::ScalarValue::Int(2)),
},
}
}),
},
}
})
}
},
})),
};
let mut frontend = Frontend::new();
frontend.apply_patch(patch1).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".to_string()), "numSeen" => Primitive::Int(2)}]}
)
);
assert_eq!(
frontend
.get_conflicts(&Path::root().key("birds").index(0))
.unwrap(),
hashmap! {
actor1.op_id_at(2) => hashmap!{
"species" => Primitive::Str("woodpecker".into()),
"numSeen" => Primitive::Int(1),
}.into(),
actor2.op_id_at(2) => hashmap!{
"species" => Primitive::Str("lapwing".into()),
"numSeen" => Primitive::Int(2),
}.into(),
}
);
let patch2 = amp::Patch {
actor: None,
seq: None,
max_op: 5,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor1.clone() => 2,
actor2.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => hashmap!{
other_actor.op_id_at(1) => amp::Diff::Seq(amp::SeqDiff{
object_id: other_actor.op_id_at(1).into(),
obj_type: amp::SequenceType::List,
edits: Vec::new(),
props: hashmap!{
0 => hashmap!{
actor1.op_id_at(2) => amp::Diff::Map(amp::MapDiff{
object_id: actor1.op_id_at(2).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"numSeen".into() => hashmap!{
actor1.op_id_at(5) => amp::Diff::Value(amp::ScalarValue::Int(2)),
},
}
}),
actor2.op_id_at(2) => amp::Diff::Unchanged(amp::ObjDiff{
object_id: actor2.op_id_at(2).into(),
obj_type: amp::ObjType::Map(amp::MapType::Map),
}),
},
}
})
}
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".to_string()), "numSeen" => Primitive::Int(2)}]}
)
);
assert_eq!(
frontend
.get_conflicts(&Path::root().key("birds").index(0))
.unwrap(),
hashmap! {
actor1.op_id_at(2) => hashmap!{
"species" => Primitive::Str("woodpecker".into()),
"numSeen" => Primitive::Int(2),
}.into(),
actor2.op_id_at(2) => hashmap!{
"species" => Primitive::Str("lapwing".into()),
"numSeen" => Primitive::Int(2),
}.into(),
}
);
}
#[test]
fn delete_list_elements() {
let actor = amp::ActorId::random();
let mut frontend = Frontend::new();
let patch = amp::Patch {
actor: None,
seq: None,
max_op: 3,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 1,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => 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("chaffinch".into())
},
1 => hashmap!{
actor.op_id_at(3) => amp::Diff::Value("goldfinch".into())
}
}
})
}
},
})),
};
frontend.apply_patch(patch).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"birds" => vec!["chaffinch", "goldfinch"]})
);
let patch2 = amp::Patch {
actor: None,
seq: None,
max_op: 4,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 2,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"birds".into() => 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::Remove{ index: 0 }],
props: hashmap!{}
})
}
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {"birds" => vec!["goldfinch"]})
);
}
#[test]
fn apply_updates_at_different_levels_of_object_tree() {
let actor = amp::ActorId::random();
let patch1 = amp::Patch {
clock: hashmap! {actor.clone() => 1},
seq: None,
max_op: 6,
pending_changes: 0,
actor: None,
deps: Vec::new(),
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"counts".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor.op_id_at(1).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"magpie".into() => hashmap!{
actor.op_id_at(2) => amp::Diff::Value(amp::ScalarValue::Int(2))
}
}
})
},
"details".into() => hashmap!{
actor.op_id_at(3) => amp::Diff::Seq(amp::SeqDiff{
object_id: actor.op_id_at(3).into(),
obj_type: amp::SequenceType::List,
edits: vec![amp::DiffEdit::Insert{ index: 0, elem_id: actor.op_id_at(4).into() }],
props: hashmap!{
0 => hashmap!{
actor.op_id_at(4) => amp::Diff::Map(amp::MapDiff{
object_id: actor.op_id_at(4).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"species".into() => hashmap!{
actor.op_id_at(5) => amp::Diff::Value("magpie".into())
},
"family".into() => hashmap!{
actor.op_id_at(6) => amp::Diff::Value("Corvidae".into())
}
}
})
}
}
})
},
},
})),
};
let mut frontend = Frontend::new();
frontend.apply_patch(patch1).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {
"counts" => Into::<Value>::into(hashmap!{"magpie".to_string() => Primitive::Int(2)}),
"details" => vec![Into::<Value>::into(hashmap!{
"species" => "magpie",
"family" => "Corvidae",
})].into()
})
);
let patch2 = amp::Patch {
clock: hashmap! {actor.clone() => 2},
seq: None,
max_op: 7,
pending_changes: 0,
actor: None,
deps: Vec::new(),
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"counts".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Map(amp::MapDiff{
object_id: actor.op_id_at(1).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"magpie".into() => hashmap!{
actor.op_id_at(7) => amp::Diff::Value(amp::ScalarValue::Int(3))
}
}
})
},
"details".into() => hashmap!{
actor.op_id_at(3) => amp::Diff::Seq(amp::SeqDiff{
object_id: actor.op_id_at(3).into(),
obj_type: amp::SequenceType::List,
edits: Vec::new(),
props: hashmap!{
0 => hashmap!{
actor.op_id_at(4) => amp::Diff::Map(amp::MapDiff{
object_id: actor.op_id_at(4).into(),
obj_type: amp::MapType::Map,
props: hashmap!{
"species".into() => hashmap!{
actor.op_id_at(8) => amp::Diff::Value("Eurasian magpie".into())
},
}
})
}
}
})
},
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(hashmap! {
"counts" => Into::<Value>::into(hashmap!{"magpie".to_string() => Primitive::Int(3)}),
"details" => vec![Into::<Value>::into(hashmap!{
"species" => "Eurasian magpie",
"family" => "Corvidae",
})].into()
})
);
}
#[test]
fn test_text_objects() {
let actor = amp::ActorId::random();
let mut frontend = Frontend::new();
let patch = amp::Patch {
actor: None,
seq: None,
max_op: 4,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 2,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"name".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Seq(amp::SeqDiff{
object_id: actor.op_id_at(1).into(),
obj_type: amp::SequenceType::Text,
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() },
amp::DiffEdit::Insert { index: 2, elem_id: actor.op_id_at(4).into() },
],
props: hashmap!{
0 => hashmap!{
actor.op_id_at(2) => amp::Diff::Value("b".into())
},
1 => hashmap!{
actor.op_id_at(3) => amp::Diff::Value("e".into())
},
2 => hashmap!{
actor.op_id_at(4) => amp::Diff::Value("n".into())
}
}
})
}
},
})),
};
frontend.apply_patch(patch).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"name" => Value::Text("ben".graphemes(true).map(|s|s.to_owned()).collect())}
)
);
let patch2 = amp::Patch {
actor: None,
seq: None,
max_op: 5,
pending_changes: 0,
deps: Vec::new(),
clock: hashmap! {
actor.clone() => 3,
},
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"name".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::Seq(amp::SeqDiff{
object_id: actor.op_id_at(1).into(),
obj_type: amp::SequenceType::Text,
edits: vec![
amp::DiffEdit::Remove { index: 1 },
],
props: hashmap!{
1 => hashmap! {
actor.op_id_at(5) => amp::Diff::Value(amp::ScalarValue::Str("i".to_string()))
}
}
})
}
},
})),
};
frontend.apply_patch(patch2).unwrap();
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"name" => Value::Text("bi".graphemes(true).map(|s|s.to_owned()).collect())}
)
);
}
#[test]
fn test_unchanged_diff_creates_empty_objects() {
let mut doc = Frontend::new();
let patch = amp::Patch {
actor: Some(doc.actor_id.clone()),
seq: Some(1),
clock: hashmap! {doc.actor_id.clone() => 1},
deps: Vec::new(),
max_op: 1,
pending_changes: 0,
diffs: Some(amp::Diff::Map(amp::MapDiff {
object_id: amp::ObjectId::Root,
obj_type: amp::MapType::Map,
props: hashmap! {
"text".to_string() => hashmap!{
"1@cfe5fefb771f4c15a716d488012cbf40".try_into().unwrap() => amp::Diff::Unchanged(amp::ObjDiff{
object_id: "1@cfe5fefb771f4c15a716d488012cbf40".try_into().unwrap(),
obj_type: amp::ObjType::Sequence(amp::SequenceType::Text)
})
}
},
})),
};
doc.apply_patch(patch).unwrap();
assert_eq!(
doc.state(),
&Value::Map(
hashmap! {"text".to_string() => Value::Text(Vec::new())},
amp::MapType::Map
),
);
}