add redo test

This commit is contained in:
appflowy 2021-08-06 22:25:09 +08:00
parent 3d837c51f4
commit c667ee0f36
7 changed files with 110 additions and 32 deletions

View File

@ -72,10 +72,10 @@ impl Document {
match self.history.undo() { match self.history.undo() {
None => Err(ErrorBuilder::new(UndoFail).build()), None => Err(ErrorBuilder::new(UndoFail).build()),
Some(undo_delta) => { Some(undo_delta) => {
let new_delta = self.data.compose(&undo_delta)?; let composed_delta = self.data.compose(&undo_delta)?;
let result = UndoResult::success(new_delta.target_len as u64); let redo_delta = undo_delta.invert(&self.data);
let redo_delta = undo_delta.invert_delta(&self.data); let result = UndoResult::success(composed_delta.target_len as u64);
self.data = new_delta; self.data = composed_delta;
self.history.add_redo(redo_delta); self.history.add_redo(redo_delta);
Ok(result) Ok(result)
@ -89,9 +89,9 @@ impl Document {
Some(redo_delta) => { Some(redo_delta) => {
let new_delta = self.data.compose(&redo_delta)?; let new_delta = self.data.compose(&redo_delta)?;
let result = UndoResult::success(new_delta.target_len as u64); let result = UndoResult::success(new_delta.target_len as u64);
let redo_delta = redo_delta.invert_delta(&self.data); let undo_delta = redo_delta.invert(&self.data);
self.data = new_delta; self.data = new_delta;
self.history.add_undo(redo_delta); self.history.add_undo(undo_delta);
Ok(result) Ok(result)
}, },
} }
@ -104,6 +104,8 @@ impl Document {
pub fn to_json(&self) -> String { self.data.to_json() } pub fn to_json(&self) -> String { self.data.to_json() }
pub fn to_string(&self) -> String { self.data.apply("").unwrap() }
pub fn data(&self) -> &Delta { &self.data } pub fn data(&self) -> &Delta { &self.data }
pub fn set_data(&mut self, data: Delta) { self.data = data; } pub fn set_data(&mut self, data: Delta) { self.data = data; }
@ -135,12 +137,15 @@ impl Document {
}); });
} }
let new_data = self.data.compose(&new_delta)?; // c = a.compose(b)
let undo_delta = new_delta.invert_delta(&self.data); // d = b.invert(a)
self.rev_id_counter += 1; // a = c.compose(d)
let composed_delta = self.data.compose(&new_delta)?;
let undo_delta = new_delta.invert(&self.data);
self.rev_id_counter += 1;
self.history.record(undo_delta); self.history.record(undo_delta);
self.data = new_data; self.data = composed_delta;
Ok(()) Ok(())
} }

View File

@ -3,7 +3,12 @@ use crate::{
errors::{ErrorBuilder, OTError, OTErrorCode}, errors::{ErrorBuilder, OTError, OTErrorCode},
}; };
use bytecount::num_chars; use bytecount::num_chars;
use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr}; use std::{
cmp::{max, min, Ordering},
fmt,
iter::FromIterator,
str::FromStr,
};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Delta { pub struct Delta {
@ -444,7 +449,7 @@ impl Delta {
/// Computes the inverse of an operation. The inverse of an operation is the /// Computes the inverse of an operation. The inverse of an operation is the
/// operation that reverts the effects of the operation /// operation that reverts the effects of the operation
pub fn invert(&self, s: &str) -> Self { pub fn invert_str(&self, s: &str) -> Self {
let mut inverted = Delta::default(); let mut inverted = Delta::default();
let chars = &mut s.chars(); let chars = &mut s.chars();
for op in &self.ops { for op in &self.ops {
@ -472,7 +477,7 @@ impl Delta {
inverted inverted
} }
pub fn invert_delta(&self, other: &Delta) -> Delta { pub fn invert(&self, other: &Delta) -> Delta {
let mut inverted = Delta::default(); let mut inverted = Delta::default();
if other.is_empty() { if other.is_empty() {
return inverted; return inverted;
@ -517,8 +522,8 @@ impl Delta {
}, },
Operation::Retain(_) => { Operation::Retain(_) => {
match op.has_attribute() { match op.has_attribute() {
true => inverted.retain(len as u64, op.get_attributes()), true => inverted_from_other(&mut inverted, op, index, index + len),
false => inverted_from_other(&mut inverted, op, index, index + len), false => inverted.retain(len as u64, op.get_attributes()),
} }
index += len; index += len;
}, },
@ -547,20 +552,26 @@ impl Delta {
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len()); let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
let mut offset: usize = 0; let mut offset: usize = 0;
let mut ops_iter = self.ops.iter(); let mut ops_iter = self.ops.iter();
let mut op = ops_iter.next(); let mut next_op = ops_iter.next();
while offset < interval.end && op.is_some() { if offset < interval.end && next_op.is_some() {
if let Some(op) = op { let op = next_op.take().unwrap();
let len = op.length() as usize;
// log::info!("{:?}", op);
while offset < len {
if offset < interval.start { if offset < interval.start {
offset += op.length() as usize; offset += min(interval.start, op.length() as usize);
} else { } else {
ops.push(op.clone()); if interval.contains(offset) {
offset += op.length() as usize; ops.push(op.shrink_to_interval(interval));
offset += max(op.length() as usize, interval.size());
} else {
break;
}
} }
} }
op = ops_iter.next(); next_op = ops_iter.next();
} }
ops ops
} }

View File

@ -1,6 +1,7 @@
use crate::core::{Attributes, OpBuilder}; use crate::core::{Attributes, Interval, OpBuilder};
use bytecount::num_chars; use bytecount::num_chars;
use std::{ use std::{
cmp::min,
fmt, fmt,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
str::Chars, str::Chars,
@ -52,9 +53,9 @@ impl Operation {
pub fn has_attribute(&self) -> bool { pub fn has_attribute(&self) -> bool {
match self.get_attributes() { match self.get_attributes() {
Attributes::Follow => true, Attributes::Follow => false,
Attributes::Custom(_) => false, Attributes::Custom(data) => data.is_empty(),
Attributes::Empty => true, Attributes::Empty => false,
} }
} }
@ -67,6 +68,30 @@ impl Operation {
} }
pub fn is_empty(&self) -> bool { self.length() == 0 } pub fn is_empty(&self) -> bool { self.length() == 0 }
pub fn shrink_to_interval(&self, interval: Interval) -> Operation {
match self {
Operation::Delete(_) => Operation::Delete(interval.size() as u64),
Operation::Retain(retain) => {
//
OpBuilder::retain(interval.size() as u64)
.attributes(retain.attributes.clone())
.build()
},
Operation::Insert(insert) => {
// debug_assert!(insert.s.len() <= interval.size());
if interval.start > insert.s.len() {
return OpBuilder::insert("").build();
}
let end = min(interval.end, insert.s.len());
let s = &insert.s[interval.start..end];
OpBuilder::insert(s)
.attributes(insert.attributes.clone())
.build()
},
}
}
} }
impl fmt::Display for Operation { impl fmt::Display for Operation {

View File

@ -35,6 +35,9 @@ pub enum TestOp {
#[display(fmt = "Redo")] #[display(fmt = "Redo")]
Redo(usize), Redo(usize),
#[display(fmt = "AssertStr")]
AssertStr(usize, &'static str),
#[display(fmt = "AssertOpsJson")] #[display(fmt = "AssertOpsJson")]
AssertOpsJson(usize, &'static str), AssertOpsJson(usize, &'static str),
} }
@ -107,7 +110,7 @@ impl OpTester {
log::debug!("b: {}", delta_b.to_json()); log::debug!("b: {}", delta_b.to_json());
let (_, b_prime) = delta_a.transform(delta_b).unwrap(); let (_, b_prime) = delta_a.transform(delta_b).unwrap();
let undo = b_prime.invert_delta(&delta_a); let undo = b_prime.invert(&delta_a);
let new_delta = delta_a.compose(&b_prime).unwrap(); let new_delta = delta_a.compose(&b_prime).unwrap();
log::debug!("new delta: {}", new_delta.to_json()); log::debug!("new delta: {}", new_delta.to_json());
@ -127,6 +130,10 @@ impl OpTester {
TestOp::Redo(delta_i) => { TestOp::Redo(delta_i) => {
self.documents[*delta_i].redo().unwrap(); self.documents[*delta_i].redo().unwrap();
}, },
TestOp::AssertStr(delta_i, expected) => {
assert_eq!(&self.documents[*delta_i].to_string(), expected);
},
TestOp::AssertOpsJson(delta_i, expected) => { TestOp::AssertOpsJson(delta_i, expected) => {
let delta_i_json = self.documents[*delta_i].to_json(); let delta_i_json = self.documents[*delta_i].to_json();

View File

@ -10,7 +10,7 @@ fn delta_invert_no_attribute_delta() {
let mut change = Delta::default(); let mut change = Delta::default();
change.add(OpBuilder::retain(3).build()); change.add(OpBuilder::retain(3).build());
change.add(OpBuilder::insert("456").build()); change.add(OpBuilder::insert("456").build());
let undo = change.invert_delta(&delta); let undo = change.invert(&delta);
let new_delta = delta.compose(&change).unwrap(); let new_delta = delta.compose(&change).unwrap();
let delta_after_undo = new_delta.compose(&undo).unwrap(); let delta_after_undo = new_delta.compose(&undo).unwrap();

View File

@ -82,7 +82,7 @@ fn invert() {
let mut rng = Rng::default(); let mut rng = Rng::default();
let s = rng.gen_string(50); let s = rng.gen_string(50);
let delta_a = rng.gen_delta(&s); let delta_a = rng.gen_delta(&s);
let delta_b = delta_a.invert(&s); let delta_b = delta_a.invert_str(&s);
assert_eq!(delta_a.base_len, delta_b.target_len); assert_eq!(delta_a.base_len, delta_b.target_len);
assert_eq!(delta_a.target_len, delta_b.base_len); assert_eq!(delta_a.target_len, delta_b.base_len);
assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s); assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);

View File

@ -3,13 +3,13 @@ pub mod helper;
use crate::helper::{TestOp::*, *}; use crate::helper::{TestOp::*, *};
#[test] #[test]
fn delta_undo_insert_text() { fn delta_undo_insert() {
let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)]; let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)];
OpTester::new().run_script(ops); OpTester::new().run_script(ops);
} }
#[test] #[test]
fn delta_undo_insert_text2() { fn delta_undo_insert2() {
let ops = vec![ let ops = vec![
Insert(0, "123", 0), Insert(0, "123", 0),
Insert(0, "456", 0), Insert(0, "456", 0),
@ -20,3 +20,33 @@ fn delta_undo_insert_text2() {
]; ];
OpTester::new().run_script(ops); OpTester::new().run_script(ops);
} }
#[test]
fn delta_redo_insert() {
let ops = vec![
Insert(0, "123", 0),
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
Undo(0),
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
Redo(0),
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
];
OpTester::new().run_script(ops);
}
#[test]
fn delta_redo_insert2() {
let ops = vec![
Insert(0, "123", 0),
Insert(0, "456", 3),
AssertStr(0, "123456\n"),
AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
Undo(0),
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
Redo(0),
AssertOpsJson(0, r#"[{"insert":"123456\n"}]"#),
Undo(0),
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
];
OpTester::new().run_script(ops);
}