mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
add undo test
This commit is contained in:
parent
760c191758
commit
3d837c51f4
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
client::{History, RevId, Revision, UndoResult},
|
||||
client::{History, RevId, UndoResult},
|
||||
core::{
|
||||
Attribute,
|
||||
Attributes,
|
||||
@ -21,14 +21,17 @@ pub struct Document {
|
||||
|
||||
impl Document {
|
||||
pub fn new() -> Self {
|
||||
let mut delta = Delta::new();
|
||||
delta.insert("\n", Attributes::Empty);
|
||||
|
||||
Document {
|
||||
data: Delta::new(),
|
||||
data: delta,
|
||||
history: History::new(),
|
||||
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 {
|
||||
log::error!(
|
||||
"{} out of bounds. should 0..{}",
|
||||
@ -44,16 +47,21 @@ impl Document {
|
||||
let insert = OpBuilder::insert(text).attributes(attributes).build();
|
||||
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 {
|
||||
true => AttrsBuilder::new().add_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() }
|
||||
@ -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();
|
||||
self.update_with_op(delete, interval);
|
||||
self.update_with_op(delete, interval)
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
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 (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 undo_delta = new_data.invert_delta(&self.data);
|
||||
let new_data = self.data.compose(&new_delta)?;
|
||||
let undo_delta = new_delta.invert_delta(&self.data);
|
||||
self.rev_id_counter += 1;
|
||||
|
||||
if !undo_delta.is_empty() {
|
||||
self.history.add_undo(undo_delta);
|
||||
}
|
||||
|
||||
self.history.record(undo_delta);
|
||||
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);
|
||||
log::debug!(
|
||||
"merge attributes: {:?}, with old: {:?}",
|
||||
@ -165,23 +175,12 @@ impl Document {
|
||||
interval
|
||||
);
|
||||
|
||||
self.update_with_op(retain, interval);
|
||||
self.update_with_op(retain, interval)
|
||||
}
|
||||
|
||||
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) {
|
||||
let original_interval = Interval::new(0, length);
|
||||
let prefix = original_interval.prefix(interval);
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::core::{Delta, Interval, OpBuilder, Operation};
|
||||
use crate::core::Delta;
|
||||
|
||||
const MAX_UNDOS: usize = 20;
|
||||
|
||||
@ -37,6 +37,7 @@ pub struct History {
|
||||
cur_undo: usize,
|
||||
undos: Vec<Delta>,
|
||||
redos: Vec<Delta>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl History {
|
||||
@ -45,6 +46,7 @@ impl History {
|
||||
cur_undo: 1,
|
||||
undos: 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 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> {
|
||||
if !self.can_undo() {
|
||||
return None;
|
||||
|
@ -1,8 +1,5 @@
|
||||
use derive_more::Display;
|
||||
use flowy_ot::{
|
||||
client::{transform, Document},
|
||||
core::*,
|
||||
};
|
||||
use flowy_ot::{client::Document, core::*};
|
||||
use rand::{prelude::*, Rng as WrappedRng};
|
||||
use std::sync::Once;
|
||||
|
||||
@ -66,30 +63,41 @@ impl OpTester {
|
||||
match op {
|
||||
TestOp::Insert(delta_i, s, index) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
document.edit(*index, s);
|
||||
document.edit(*index, s).unwrap();
|
||||
},
|
||||
TestOp::Delete(delta_i, interval) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
document.delete(*interval);
|
||||
document.delete(*interval).unwrap();
|
||||
},
|
||||
TestOp::InsertBold(delta_i, s, interval) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
document.edit(interval.start, s);
|
||||
document.format(*interval, Attribute::Bold, true);
|
||||
document.edit(interval.start, s).unwrap();
|
||||
document.format(*interval, Attribute::Bold, true).unwrap();
|
||||
},
|
||||
TestOp::Bold(delta_i, interval, enable) => {
|
||||
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) => {
|
||||
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) => {
|
||||
transform(
|
||||
&mut self.documents[*delta_a_i],
|
||||
&mut self.documents[*delta_b_i],
|
||||
);
|
||||
let (a_prime, b_prime) = self.documents[*delta_a_i]
|
||||
.data()
|
||||
.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) => {
|
||||
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);
|
||||
},
|
||||
TestOp::Undo(delta_i) => {
|
||||
self.documents[*delta_i].undo();
|
||||
self.documents[*delta_i].undo().unwrap();
|
||||
},
|
||||
TestOp::Redo(delta_i) => {
|
||||
self.documents[*delta_i].redo();
|
||||
self.documents[*delta_i].redo().unwrap();
|
||||
},
|
||||
TestOp::AssertOpsJson(delta_i, expected) => {
|
||||
let delta_i_json = self.documents[*delta_i].to_json();
|
||||
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user