add undo test

This commit is contained in:
appflowy 2021-08-06 08:40:45 +08:00
parent 760c191758
commit 3d837c51f4
4 changed files with 89 additions and 45 deletions

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
client::{History, RevId, Revision, UndoResult}, client::{History, RevId, UndoResult},
core::{ core::{
Attribute, Attribute,
Attributes, Attributes,
@ -21,14 +21,17 @@ pub struct Document {
impl Document { impl Document {
pub fn new() -> Self { pub fn new() -> Self {
let mut delta = Delta::new();
delta.insert("\n", Attributes::Empty);
Document { Document {
data: Delta::new(), data: delta,
history: History::new(), history: History::new(),
rev_id_counter: 1, rev_id_counter: 1,
} }
} }
pub fn edit(&mut self, index: usize, text: &str) { pub fn edit(&mut self, index: usize, text: &str) -> Result<(), OTError> {
if self.data.target_len < index { if self.data.target_len < index {
log::error!( log::error!(
"{} out of bounds. should 0..{}", "{} out of bounds. should 0..{}",
@ -44,16 +47,21 @@ impl Document {
let insert = OpBuilder::insert(text).attributes(attributes).build(); let insert = OpBuilder::insert(text).attributes(attributes).build();
let interval = Interval::new(index, index); let interval = Interval::new(index, index);
self.update_with_op(insert, interval); self.update_with_op(insert, interval)
} }
pub fn format(&mut self, interval: Interval, attribute: Attribute, enable: bool) { pub fn format(
&mut self,
interval: Interval,
attribute: Attribute,
enable: bool,
) -> Result<(), OTError> {
let attributes = match enable { let attributes = match enable {
true => AttrsBuilder::new().add_attribute(attribute).build(), true => AttrsBuilder::new().add_attribute(attribute).build(),
false => AttrsBuilder::new().remove_attribute(attribute).build(), false => AttrsBuilder::new().remove_attribute(attribute).build(),
}; };
self.update_with_attribute(attributes, interval); self.update_with_attribute(attributes, interval)
} }
pub fn can_undo(&self) -> bool { self.history.can_undo() } pub fn can_undo(&self) -> bool { self.history.can_undo() }
@ -89,9 +97,9 @@ impl Document {
} }
} }
pub fn delete(&mut self, interval: Interval) { pub fn delete(&mut self, interval: Interval) -> Result<(), OTError> {
let delete = OpBuilder::delete(interval.size() as u64).build(); let delete = OpBuilder::delete(interval.size() as u64).build();
self.update_with_op(delete, interval); self.update_with_op(delete, interval)
} }
pub fn to_json(&self) -> String { self.data.to_json() } pub fn to_json(&self) -> String { self.data.to_json() }
@ -100,7 +108,7 @@ impl Document {
pub fn set_data(&mut self, data: Delta) { self.data = data; } pub fn set_data(&mut self, data: Delta) { self.data = data; }
fn update_with_op(&mut self, op: Operation, interval: Interval) { fn update_with_op(&mut self, op: Operation, interval: Interval) -> Result<(), OTError> {
let mut new_delta = Delta::default(); let mut new_delta = Delta::default();
let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval); let (prefix, interval, suffix) = split_length_with_interval(self.data.target_len, interval);
@ -127,18 +135,20 @@ impl Document {
}); });
} }
let new_data = self.data.compose(&new_delta).unwrap(); let new_data = self.data.compose(&new_delta)?;
let undo_delta = new_data.invert_delta(&self.data); let undo_delta = new_delta.invert_delta(&self.data);
self.rev_id_counter += 1; self.rev_id_counter += 1;
if !undo_delta.is_empty() { self.history.record(undo_delta);
self.history.add_undo(undo_delta);
}
self.data = new_data; self.data = new_data;
Ok(())
} }
pub fn update_with_attribute(&mut self, mut attributes: Attributes, interval: Interval) { pub fn update_with_attribute(
&mut self,
mut attributes: Attributes,
interval: Interval,
) -> Result<(), OTError> {
let old_attributes = self.data.get_attributes(interval); let old_attributes = self.data.get_attributes(interval);
log::debug!( log::debug!(
"merge attributes: {:?}, with old: {:?}", "merge attributes: {:?}, with old: {:?}",
@ -165,23 +175,12 @@ impl Document {
interval interval
); );
self.update_with_op(retain, interval); self.update_with_op(retain, interval)
} }
fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) } fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) }
} }
pub fn transform(left: &mut Document, right: &mut Document) {
let (a_prime, b_prime) = left.data.transform(&right.data).unwrap();
log::trace!("a:{:?},b:{:?}", a_prime, b_prime);
let data_left = left.data.compose(&b_prime).unwrap();
let data_right = right.data.compose(&a_prime).unwrap();
left.set_data(data_left);
right.set_data(data_right);
}
fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) { fn split_length_with_interval(length: usize, interval: Interval) -> (Interval, Interval, Interval) {
let original_interval = Interval::new(0, length); let original_interval = Interval::new(0, length);
let prefix = original_interval.prefix(interval); let prefix = original_interval.prefix(interval);

View File

@ -1,4 +1,4 @@
use crate::core::{Delta, Interval, OpBuilder, Operation}; use crate::core::Delta;
const MAX_UNDOS: usize = 20; const MAX_UNDOS: usize = 20;
@ -37,6 +37,7 @@ pub struct History {
cur_undo: usize, cur_undo: usize,
undos: Vec<Delta>, undos: Vec<Delta>,
redos: Vec<Delta>, redos: Vec<Delta>,
capacity: usize,
} }
impl History { impl History {
@ -45,6 +46,7 @@ impl History {
cur_undo: 1, cur_undo: 1,
undos: Vec::new(), undos: Vec::new(),
redos: Vec::new(), redos: Vec::new(),
capacity: 20,
} }
} }
@ -56,6 +58,19 @@ impl History {
pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); } pub fn add_redo(&mut self, delta: Delta) { self.redos.push(delta); }
pub fn record(&mut self, delta: Delta) {
if delta.ops.is_empty() {
return;
}
self.redos.clear();
self.add_undo(delta);
if self.undos.len() > self.capacity {
self.undos.remove(0);
}
}
pub fn undo(&mut self) -> Option<Delta> { pub fn undo(&mut self) -> Option<Delta> {
if !self.can_undo() { if !self.can_undo() {
return None; return None;

View File

@ -1,8 +1,5 @@
use derive_more::Display; use derive_more::Display;
use flowy_ot::{ use flowy_ot::{client::Document, core::*};
client::{transform, Document},
core::*,
};
use rand::{prelude::*, Rng as WrappedRng}; use rand::{prelude::*, Rng as WrappedRng};
use std::sync::Once; use std::sync::Once;
@ -66,30 +63,41 @@ impl OpTester {
match op { match op {
TestOp::Insert(delta_i, s, index) => { TestOp::Insert(delta_i, s, index) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
document.edit(*index, s); document.edit(*index, s).unwrap();
}, },
TestOp::Delete(delta_i, interval) => { TestOp::Delete(delta_i, interval) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
document.delete(*interval); document.delete(*interval).unwrap();
}, },
TestOp::InsertBold(delta_i, s, interval) => { TestOp::InsertBold(delta_i, s, interval) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
document.edit(interval.start, s); document.edit(interval.start, s).unwrap();
document.format(*interval, Attribute::Bold, true); document.format(*interval, Attribute::Bold, true).unwrap();
}, },
TestOp::Bold(delta_i, interval, enable) => { TestOp::Bold(delta_i, interval, enable) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
document.format(*interval, Attribute::Bold, *enable); document
.format(*interval, Attribute::Bold, *enable)
.unwrap();
}, },
TestOp::Italic(delta_i, interval, enable) => { TestOp::Italic(delta_i, interval, enable) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
document.format(*interval, Attribute::Italic, *enable); document
.format(*interval, Attribute::Italic, *enable)
.unwrap();
}, },
TestOp::Transform(delta_a_i, delta_b_i) => { TestOp::Transform(delta_a_i, delta_b_i) => {
transform( let (a_prime, b_prime) = self.documents[*delta_a_i]
&mut self.documents[*delta_a_i], .data()
&mut self.documents[*delta_b_i], .transform(&self.documents[*delta_b_i].data())
); .unwrap();
log::trace!("a:{:?},b:{:?}", a_prime, b_prime);
let data_left = self.documents[*delta_a_i].data().compose(&b_prime).unwrap();
let data_right = self.documents[*delta_b_i].data().compose(&a_prime).unwrap();
self.documents[*delta_a_i].set_data(data_left);
self.documents[*delta_b_i].set_data(data_right);
}, },
TestOp::Invert(delta_a_i, delta_b_i) => { TestOp::Invert(delta_a_i, delta_b_i) => {
let delta_a = &self.documents[*delta_a_i].data(); let delta_a = &self.documents[*delta_a_i].data();
@ -114,10 +122,10 @@ impl OpTester {
self.documents[*delta_a_i].set_data(new_delta_after_undo); self.documents[*delta_a_i].set_data(new_delta_after_undo);
}, },
TestOp::Undo(delta_i) => { TestOp::Undo(delta_i) => {
self.documents[*delta_i].undo(); self.documents[*delta_i].undo().unwrap();
}, },
TestOp::Redo(delta_i) => { TestOp::Redo(delta_i) => {
self.documents[*delta_i].redo(); self.documents[*delta_i].redo().unwrap();
}, },
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

@ -0,0 +1,22 @@
pub mod helper;
use crate::helper::{TestOp::*, *};
#[test]
fn delta_undo_insert_text() {
let ops = vec![Insert(0, "123", 0), Undo(0), AssertOpsJson(0, r#"[]"#)];
OpTester::new().run_script(ops);
}
#[test]
fn delta_undo_insert_text2() {
let ops = vec![
Insert(0, "123", 0),
Insert(0, "456", 0),
Undo(0),
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
Undo(0),
AssertOpsJson(0, r#"[{"insert":"\n"}]"#),
];
OpTester::new().run_script(ops);
}