use super::*;
use automerge::ReadDoc as AmReadDoc;
use std::borrow::Cow;
struct KeyedItem {
id: String,
value: String,
}
impl Reconcile for KeyedItem {
type Key<'a> = Cow<'a, String>;
fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
let mut m = reconciler.map()?;
m.put("id", &self.id)?;
m.put("value", &self.value)?;
Ok(())
}
fn hydrate_key<'a, D: crate::ReadDoc>(
doc: &D,
obj: &automerge::ObjId,
prop: crate::Prop<'_>,
) -> Result<LoadKey<Self::Key<'a>>, ReconcileError> {
crate::hydrate_key(doc, obj, prop, "id".into())
}
fn key(&self) -> LoadKey<Self::Key<'_>> {
LoadKey::Found(Cow::Borrowed(&self.id))
}
}
impl crate::Hydrate for KeyedItem {
fn hydrate_map<D: crate::ReadDoc>(
doc: &D,
obj: &automerge::ObjId,
) -> Result<Self, crate::HydrateError> {
let id: String = crate::hydrate_prop(doc, obj, "id")?;
let value: String = crate::hydrate_prop(doc, obj, "value")?;
Ok(KeyedItem { id, value })
}
}
struct FreshItem {
id: String,
value: String,
}
impl Reconcile for FreshItem {
type Key<'a> = Cow<'a, String>;
fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
let mut m = reconciler.map()?;
m.put("id", &self.id)?;
m.put("value", &self.value)?;
Ok(())
}
fn hydrate_key<'a, D: crate::ReadDoc>(
doc: &D,
obj: &automerge::ObjId,
prop: crate::Prop<'_>,
) -> Result<LoadKey<Self::Key<'a>>, ReconcileError> {
crate::hydrate_key(doc, obj, prop, "id".into())
}
fn key(&self) -> LoadKey<Self::Key<'_>> {
LoadKey::KeyNotFound
}
}
struct MapContainer<T> {
item: T,
}
impl<T: Reconcile> Reconcile for MapContainer<T> {
type Key<'a> = NoKey;
fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
let mut m = reconciler.map()?;
m.put("item", &self.item)?;
Ok(())
}
}
struct SeqContainer {
items: Vec<KeyedItem>,
}
impl Reconcile for SeqContainer {
type Key<'a> = NoKey;
fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
let mut m = reconciler.map()?;
m.put("items", &self.items)?;
Ok(())
}
}
struct FreshSeqContainer {
items: Vec<FreshItem>,
}
impl Reconcile for FreshSeqContainer {
type Key<'a> = NoKey;
fn reconcile<R: Reconciler>(&self, mut reconciler: R) -> Result<(), R::Error> {
let mut m = reconciler.map()?;
m.put("items", &self.items)?;
Ok(())
}
}
#[test]
fn map_put_matching_keys_updates_in_place() {
let mut doc = automerge::AutoCommit::new();
let container = MapContainer {
item: KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
},
};
reconcile(&mut doc, &container).unwrap();
let mut doc2 = doc.fork().with_actor("actor2".as_bytes().into());
let item: KeyedItem = crate::hydrate_prop(&doc2, &automerge::ROOT, "item").unwrap();
let container2 = MapContainer {
item: KeyedItem {
id: "item1".to_string(), value: "updated".to_string(),
},
};
reconcile(&mut doc, &container2).unwrap();
reconcile_prop(
&mut doc2,
automerge::ROOT,
"item",
&KeyedItem {
id: item.id,
value: "concurrent".to_string(),
},
)
.unwrap();
doc.merge(&mut doc2).unwrap();
let (val, item_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
assert!(matches!(
val,
automerge::Value::Object(automerge::ObjType::Map)
));
let values = AmReadDoc::get_all(&doc, &item_id, "value").unwrap();
assert_eq!(values.len(), 2, "Expected conflict with 2 values");
}
#[test]
fn map_put_different_keys_creates_new_object() {
let mut doc = automerge::AutoCommit::new();
let container = MapContainer {
item: KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
},
};
reconcile(&mut doc, &container).unwrap();
let (_, original_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
let container2 = MapContainer {
item: KeyedItem {
id: "item2".to_string(), value: "new_item".to_string(),
},
};
reconcile(&mut doc, &container2).unwrap();
let (_, new_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
assert_ne!(
original_obj_id, new_obj_id,
"Expected a new object to be created when keys don't match"
);
let hydrated: KeyedItem = crate::hydrate_prop(&doc, &automerge::ROOT, "item").unwrap();
assert_eq!(hydrated.id, "item2");
assert_eq!(hydrated.value, "new_item");
}
#[test]
fn map_put_fresh_over_keyed_creates_new_object() {
let mut doc = automerge::AutoCommit::new();
let container = MapContainer {
item: KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
},
};
reconcile(&mut doc, &container).unwrap();
let (_, original_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
let container2 = MapContainer {
item: FreshItem {
id: "item1".to_string(), value: "fresh_value".to_string(),
},
};
reconcile(&mut doc, &container2).unwrap();
let (_, new_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
assert_ne!(
original_obj_id, new_obj_id,
"Expected a new object to be created when incoming item has no key"
);
}
#[test]
fn seq_set_matching_keys_updates_in_place() {
let mut doc = automerge::AutoCommit::new();
let container = SeqContainer {
items: vec![KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
}],
};
reconcile(&mut doc, &container).unwrap();
let mut doc2 = doc.fork().with_actor("actor2".as_bytes().into());
let container2 = SeqContainer {
items: vec![KeyedItem {
id: "item1".to_string(), value: "updated".to_string(),
}],
};
reconcile(&mut doc, &container2).unwrap();
let container3 = SeqContainer {
items: vec![KeyedItem {
id: "item1".to_string(),
value: "concurrent".to_string(),
}],
};
reconcile(&mut doc2, &container3).unwrap();
doc.merge(&mut doc2).unwrap();
let (_, items_id) = AmReadDoc::get(&doc, &automerge::ROOT, "items")
.unwrap()
.unwrap();
let (_, item_id) = AmReadDoc::get(&doc, &items_id, 0_usize).unwrap().unwrap();
let values = AmReadDoc::get_all(&doc, &item_id, "value").unwrap();
assert_eq!(
values.len(),
2,
"Expected conflict with 2 values on same object"
);
}
#[test]
fn seq_set_different_keys_creates_new_object() {
let mut doc = automerge::AutoCommit::new();
let container = SeqContainer {
items: vec![KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
}],
};
reconcile(&mut doc, &container).unwrap();
let (_, items_id) = AmReadDoc::get(&doc, &automerge::ROOT, "items")
.unwrap()
.unwrap();
let (_, original_obj_id) = AmReadDoc::get(&doc, &items_id, 0_usize).unwrap().unwrap();
let container2 = SeqContainer {
items: vec![KeyedItem {
id: "item2".to_string(), value: "new_item".to_string(),
}],
};
reconcile(&mut doc, &container2).unwrap();
let (_, new_obj_id) = AmReadDoc::get(&doc, &items_id, 0_usize).unwrap().unwrap();
assert_ne!(
original_obj_id, new_obj_id,
"Expected a new object to be created when keys don't match"
);
let items: Vec<KeyedItem> = crate::hydrate_prop(&doc, &automerge::ROOT, "items").unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].id, "item2");
assert_eq!(items[0].value, "new_item");
}
#[test]
fn seq_set_fresh_over_keyed_creates_new_object() {
let mut doc = automerge::AutoCommit::new();
let container = SeqContainer {
items: vec![KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
}],
};
reconcile(&mut doc, &container).unwrap();
let (_, items_id) = AmReadDoc::get(&doc, &automerge::ROOT, "items")
.unwrap()
.unwrap();
let (_, original_obj_id) = AmReadDoc::get(&doc, &items_id, 0_usize).unwrap().unwrap();
let container2 = FreshSeqContainer {
items: vec![FreshItem {
id: "item1".to_string(),
value: "fresh_value".to_string(),
}],
};
reconcile(&mut doc, &container2).unwrap();
let (_, new_obj_id) = AmReadDoc::get(&doc, &items_id, 0_usize).unwrap().unwrap();
assert_ne!(
original_obj_id, new_obj_id,
"Expected a new object to be created when incoming item has no key"
);
}
#[test]
fn reconcile_prop_matching_keys_updates_in_place() {
let mut doc = automerge::AutoCommit::new();
let item = KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
};
reconcile_prop(&mut doc, automerge::ROOT, "item", &item).unwrap();
let mut doc2 = doc.fork().with_actor("actor2".as_bytes().into());
let item2 = KeyedItem {
id: "item1".to_string(),
value: "updated".to_string(),
};
reconcile_prop(&mut doc, automerge::ROOT, "item", &item2).unwrap();
let item3 = KeyedItem {
id: "item1".to_string(),
value: "concurrent".to_string(),
};
reconcile_prop(&mut doc2, automerge::ROOT, "item", &item3).unwrap();
doc.merge(&mut doc2).unwrap();
let (_, item_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
let values = AmReadDoc::get_all(&doc, &item_id, "value").unwrap();
assert_eq!(
values.len(),
2,
"Expected conflict with 2 values on same object"
);
}
#[test]
fn reconcile_prop_different_keys_creates_new_object() {
let mut doc = automerge::AutoCommit::new();
let item = KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
};
reconcile_prop(&mut doc, automerge::ROOT, "item", &item).unwrap();
let (_, original_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
let item2 = KeyedItem {
id: "item2".to_string(),
value: "new_item".to_string(),
};
reconcile_prop(&mut doc, automerge::ROOT, "item", &item2).unwrap();
let (_, new_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
assert_ne!(
original_obj_id, new_obj_id,
"Expected a new object to be created when keys don't match"
);
let hydrated: KeyedItem = crate::hydrate_prop(&doc, &automerge::ROOT, "item").unwrap();
assert_eq!(hydrated.id, "item2");
assert_eq!(hydrated.value, "new_item");
}
#[test]
fn reconcile_prop_fresh_over_keyed_creates_new_object() {
let mut doc = automerge::AutoCommit::new();
let item = KeyedItem {
id: "item1".to_string(),
value: "original".to_string(),
};
reconcile_prop(&mut doc, automerge::ROOT, "item", &item).unwrap();
let (_, original_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
let item2 = FreshItem {
id: "item1".to_string(),
value: "fresh_value".to_string(),
};
reconcile_prop(&mut doc, automerge::ROOT, "item", &item2).unwrap();
let (_, new_obj_id) = AmReadDoc::get(&doc, &automerge::ROOT, "item")
.unwrap()
.unwrap();
assert_ne!(
original_obj_id, new_obj_id,
"Expected a new object to be created when incoming item has no key"
);
}