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() {
None => Err(ErrorBuilder::new(UndoFail).build()),
Some(undo_delta) => {
let new_delta = self.data.compose(&undo_delta)?;
let result = UndoResult::success(new_delta.target_len as u64);
let redo_delta = undo_delta.invert_delta(&self.data);
self.data = new_delta;
let composed_delta = self.data.compose(&undo_delta)?;
let redo_delta = undo_delta.invert(&self.data);
let result = UndoResult::success(composed_delta.target_len as u64);
self.data = composed_delta;
self.history.add_redo(redo_delta);
Ok(result)
@ -89,9 +89,9 @@ impl Document {
Some(redo_delta) => {
let new_delta = self.data.compose(&redo_delta)?;
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.history.add_undo(redo_delta);
self.history.add_undo(undo_delta);
Ok(result)
},
}
@ -104,6 +104,8 @@ impl Document {
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 set_data(&mut self, data: Delta) { self.data = data; }
@ -135,12 +137,15 @@ impl Document {
});
}
let new_data = self.data.compose(&new_delta)?;
let undo_delta = new_delta.invert_delta(&self.data);
self.rev_id_counter += 1;
// c = a.compose(b)
// d = b.invert(a)
// 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.data = new_data;
self.data = composed_delta;
Ok(())
}

View File

@ -3,7 +3,12 @@ use crate::{
errors::{ErrorBuilder, OTError, OTErrorCode},
};
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)]
pub struct Delta {
@ -444,7 +449,7 @@ impl Delta {
/// Computes the inverse of an operation. The inverse of an operation is the
/// 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 chars = &mut s.chars();
for op in &self.ops {
@ -472,7 +477,7 @@ impl Delta {
inverted
}
pub fn invert_delta(&self, other: &Delta) -> Delta {
pub fn invert(&self, other: &Delta) -> Delta {
let mut inverted = Delta::default();
if other.is_empty() {
return inverted;
@ -517,8 +522,8 @@ impl Delta {
},
Operation::Retain(_) => {
match op.has_attribute() {
true => inverted.retain(len as u64, op.get_attributes()),
false => inverted_from_other(&mut inverted, op, index, index + len),
true => inverted_from_other(&mut inverted, op, index, index + len),
false => inverted.retain(len as u64, op.get_attributes()),
}
index += len;
},
@ -547,20 +552,26 @@ impl Delta {
let mut ops: Vec<Operation> = Vec::with_capacity(self.ops.len());
let mut offset: usize = 0;
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 let Some(op) = op {
if offset < interval.end && next_op.is_some() {
let op = next_op.take().unwrap();
let len = op.length() as usize;
// log::info!("{:?}", op);
while offset < len {
if offset < interval.start {
offset += op.length() as usize;
offset += min(interval.start, op.length() as usize);
} else {
ops.push(op.clone());
offset += op.length() as usize;
if interval.contains(offset) {
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
}

View File

@ -1,6 +1,7 @@
use crate::core::{Attributes, OpBuilder};
use crate::core::{Attributes, Interval, OpBuilder};
use bytecount::num_chars;
use std::{
cmp::min,
fmt,
ops::{Deref, DerefMut},
str::Chars,
@ -52,9 +53,9 @@ impl Operation {
pub fn has_attribute(&self) -> bool {
match self.get_attributes() {
Attributes::Follow => true,
Attributes::Custom(_) => false,
Attributes::Empty => true,
Attributes::Follow => false,
Attributes::Custom(data) => data.is_empty(),
Attributes::Empty => false,
}
}
@ -67,6 +68,30 @@ impl Operation {
}
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 {

View File

@ -35,6 +35,9 @@ pub enum TestOp {
#[display(fmt = "Redo")]
Redo(usize),
#[display(fmt = "AssertStr")]
AssertStr(usize, &'static str),
#[display(fmt = "AssertOpsJson")]
AssertOpsJson(usize, &'static str),
}
@ -107,7 +110,7 @@ impl OpTester {
log::debug!("b: {}", delta_b.to_json());
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();
log::debug!("new delta: {}", new_delta.to_json());
@ -127,6 +130,10 @@ impl OpTester {
TestOp::Redo(delta_i) => {
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) => {
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();
change.add(OpBuilder::retain(3).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 delta_after_undo = new_delta.compose(&undo).unwrap();

View File

@ -82,7 +82,7 @@ fn invert() {
let mut rng = Rng::default();
let s = rng.gen_string(50);
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.target_len, delta_b.base_len);
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::*, *};
#[test]
fn delta_undo_insert_text() {
fn delta_undo_insert() {
let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)];
OpTester::new().run_script(ops);
}
#[test]
fn delta_undo_insert_text2() {
fn delta_undo_insert2() {
let ops = vec![
Insert(0, "123", 0),
Insert(0, "456", 0),
@ -20,3 +20,33 @@ fn delta_undo_insert_text2() {
];
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);
}