From 4ebeac13f5b24b9ab42b4e1403dcf7a75e1828cc Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 3 Aug 2021 23:13:20 +0800 Subject: [PATCH] insert text with index --- rust-lib/flowy-ot/src/attributes.rs | 25 +++- rust-lib/flowy-ot/src/delta.rs | 12 +- rust-lib/flowy-ot/src/interval.rs | 16 ++ rust-lib/flowy-ot/src/operation.rs | 36 +++++ rust-lib/flowy-ot/tests/attribute_test.rs | 169 +++++++++++++++++++--- rust-lib/flowy-ot/tests/helper/mod.rs | 136 +++++++++++++---- rust-lib/flowy-ot/tests/op_test.rs | 6 +- 7 files changed, 343 insertions(+), 57 deletions(-) diff --git a/rust-lib/flowy-ot/src/attributes.rs b/rust-lib/flowy-ot/src/attributes.rs index 5583dc986e..464b3f272c 100644 --- a/rust-lib/flowy-ot/src/attributes.rs +++ b/rust-lib/flowy-ot/src/attributes.rs @@ -1,5 +1,5 @@ use crate::operation::Operation; -use std::collections::HashMap; +use std::{collections::HashMap, fmt}; const PLAIN: &'static str = ""; fn is_plain(s: &str) -> bool { s == PLAIN } @@ -15,7 +15,7 @@ pub enum Attributes { } impl Attributes { - pub fn merge(&self, other: Option) -> Attributes { + pub fn extend(&self, other: Option) -> Attributes { let other = other.unwrap_or(Attributes::Empty); match (self, &other) { (Attributes::Custom(data), Attributes::Custom(o_data)) => { @@ -42,6 +42,23 @@ impl std::default::Default for Attributes { fn default() -> Self { Attributes::Empty } } +impl fmt::Display for Attributes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Attributes::Follow => { + f.write_str("")?; + }, + Attributes::Custom(data) => { + f.write_fmt(format_args!("{:?}", data.inner))?; + }, + Attributes::Empty => { + f.write_str("")?; + }, + } + Ok(()) + } +} + #[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct AttributesData { #[serde(skip_serializing_if = "HashMap::is_empty")] @@ -132,9 +149,9 @@ pub fn compose_attributes(left: &Option, right: &Option) - let mut attr = match (&attr_l, &attr_r) { (_, Some(Attributes::Custom(_))) => match &attr_l { None => attr_r.unwrap(), - Some(_) => attr_l.unwrap().merge(attr_r.clone()), + Some(_) => attr_l.unwrap().extend(attr_r.clone()), }, - (Some(Attributes::Custom(_)), _) => attr_l.unwrap().merge(attr_r), + (Some(Attributes::Custom(_)), _) => attr_l.unwrap().extend(attr_r), _ => Attributes::Empty, }; diff --git a/rust-lib/flowy-ot/src/delta.rs b/rust-lib/flowy-ot/src/delta.rs index df23dd3356..4df9441e50 100644 --- a/rust-lib/flowy-ot/src/delta.rs +++ b/rust-lib/flowy-ot/src/delta.rs @@ -1,6 +1,6 @@ use crate::{attributes::*, errors::OTError, operation::*}; use bytecount::num_chars; -use std::{cmp::Ordering, iter::FromIterator, str::FromStr}; +use std::{cmp::Ordering, fmt, iter::FromIterator, str::FromStr}; #[derive(Clone, Debug, PartialEq)] pub struct Delta { @@ -33,6 +33,16 @@ impl> From for Delta { fn from(s: T) -> Delta { Delta::from_str(s.as_ref()).unwrap() } } +impl fmt::Display for Delta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?; + // for op in &self.ops { + // f.write_fmt(format_args!("{}", op)); + // } + Ok(()) + } +} + impl FromIterator for Delta { fn from_iter>(ops: T) -> Self { let mut operations = Delta::default(); diff --git a/rust-lib/flowy-ot/src/interval.rs b/rust-lib/flowy-ot/src/interval.rs index e5d9a6422b..10d3e9fe26 100644 --- a/rust-lib/flowy-ot/src/interval.rs +++ b/rust-lib/flowy-ot/src/interval.rs @@ -77,9 +77,25 @@ impl Interval { } } + pub fn union(&self, other: Interval) -> Interval { + if self.is_empty() { + return other; + } + if other.is_empty() { + return *self; + } + let start = min(self.start, other.start); + let end = max(self.end, other.end); + Interval { start, end } + } + pub fn size(&self) -> usize { self.end - self.start } } +impl std::default::Default for Interval { + fn default() -> Self { Interval::new(0, 0) } +} + impl fmt::Display for Interval { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}, {})", self.start(), self.end()) diff --git a/rust-lib/flowy-ot/src/operation.rs b/rust-lib/flowy-ot/src/operation.rs index 5f476ec280..4d11c51ea9 100644 --- a/rust-lib/flowy-ot/src/operation.rs +++ b/rust-lib/flowy-ot/src/operation.rs @@ -1,6 +1,7 @@ use crate::attributes::Attributes; use bytecount::num_chars; use std::{ + fmt, ops::{Deref, DerefMut}, str::Chars, }; @@ -35,6 +36,18 @@ impl Operation { } } + pub fn extend_attributes(&mut self, attributes: Attributes) { + match self { + Operation::Delete(_) => {}, + Operation::Retain(retain) => { + retain.attributes.extend(Some(attributes)); + }, + Operation::Insert(insert) => { + insert.attributes.extend(Some(attributes)); + }, + } + } + pub fn set_attributes(&mut self, attributes: Attributes) { match self { Operation::Delete(_) => { @@ -67,6 +80,29 @@ impl Operation { pub fn is_empty(&self) -> bool { self.length() == 0 } } +impl fmt::Display for Operation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Operation::Delete(n) => { + f.write_fmt(format_args!("delete: {}", n))?; + }, + Operation::Retain(r) => { + f.write_fmt(format_args!( + "retain: {}, attributes: {}", + r.num, r.attributes + ))?; + }, + Operation::Insert(i) => { + f.write_fmt(format_args!( + "insert: {}, attributes: {}", + i.s, i.attributes + ))?; + }, + } + Ok(()) + } +} + pub struct OpBuilder { ty: Operation, attrs: Attributes, diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index f32727381b..8d6d768482 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -6,10 +6,40 @@ use flowy_ot::{ operation::{OpBuilder, Operation, Retain}, }; +#[test] +fn delta_insert_text() { + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "456", 3), + AssertOpsJson(0, r#"[{"insert":"123456"}]"#), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_insert_text_at_head() { + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "456", 0), + AssertOpsJson(0, r#"[{"insert":"456123"}]"#), + ]; + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_insert_text_at_middle() { + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "456", 2), + AssertOpsJson(0, r#"[{"insert":"124563"}]"#), + ]; + MergeTest::new().run_script(ops); +} + #[test] fn delta_add_bold_and_invert_all() { let ops = vec![ - Insert(0, "123"), + Insert(0, "123", 0), Bold(0, Interval::new(0, 3), true), AssertOpsJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}}]"#), Bold(0, Interval::new(0, 3), false), @@ -21,7 +51,7 @@ fn delta_add_bold_and_invert_all() { #[test] fn delta_add_bold_and_invert_partial_suffix() { let ops = vec![ - Insert(0, "1234"), + Insert(0, "1234", 0), Bold(0, Interval::new(0, 4), true), AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), Bold(0, Interval::new(2, 4), false), @@ -36,7 +66,7 @@ fn delta_add_bold_and_invert_partial_suffix() { #[test] fn delta_add_bold_and_invert_partial_suffix2() { let ops = vec![ - Insert(0, "1234"), + Insert(0, "1234", 0), Bold(0, Interval::new(0, 4), true), AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), Bold(0, Interval::new(2, 4), false), @@ -53,7 +83,7 @@ fn delta_add_bold_and_invert_partial_suffix2() { #[test] fn delta_add_bold_and_invert_partial_prefix() { let ops = vec![ - Insert(0, "1234"), + Insert(0, "1234", 0), Bold(0, Interval::new(0, 4), true), AssertOpsJson(0, r#"[{"insert":"1234","attributes":{"bold":"true"}}]"#), Bold(0, Interval::new(0, 2), false), @@ -68,7 +98,7 @@ fn delta_add_bold_and_invert_partial_prefix() { #[test] fn delta_add_bold_consecutive() { let ops = vec![ - Insert(0, "1234"), + Insert(0, "1234", 0), Bold(0, Interval::new(0, 1), true), AssertOpsJson( 0, @@ -93,14 +123,14 @@ fn delta_add_bold_empty_str() { #[test] fn delta_add_bold_italic() { let ops = vec![ - Insert(0, "1234"), + Insert(0, "1234", 0), Bold(0, Interval::new(0, 4), true), Italic(0, Interval::new(0, 4), true), AssertOpsJson( 0, r#"[{"insert":"1234","attributes":{"italic":"true","bold":"true"}}]"#, ), - Insert(0, "5678"), + Insert(0, "5678", 4), AssertOpsJson( 0, r#"[{"insert":"12345678","attributes":{"italic":"true","bold":"true"}}]"#, @@ -114,6 +144,74 @@ fn delta_add_bold_italic() { MergeTest::new().run_script(ops); } +#[test] +fn delta_add_bold_italic2() { + let ops = vec![ + Insert(0, "123456", 0), + Bold(0, Interval::new(0, 6), true), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Italic(0, Interval::new(0, 2), true), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"italic":"true","bold":"true"}},{"insert":"3456","attributes":{"bold":"true"}}]"#, + ), + Italic(0, Interval::new(4, 6), true), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"bold":"true","italic":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"true"}}]"#, + ), + ]; + + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_add_bold_italic3() { + let ops = vec![ + Insert(0, "123456789", 0), + Bold(0, Interval::new(0, 5), true), + Italic(0, Interval::new(0, 2), true), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"bold":"true","italic":"true"}},{"insert":"345","attributes":{"bold":"true"}},{"insert":"6789"}]"#, + ), + Italic(0, Interval::new(2, 4), true), + AssertOpsJson( + 0, + r#"[{"insert":"1234","attributes":{"bold":"true","italic":"true"}},{"insert":"5","attributes":{"bold":"true"}},{"insert":"6789"}]"#, + ), + Bold(0, Interval::new(7, 9), true), + AssertOpsJson( + 0, + r#"[{"insert":"1234","attributes":{"bold":"true","italic":"true"}},{"insert":"5","attributes":{"bold":"true"}},{"insert":"67"},{"insert":"89","attributes":{"bold":"true"}}]"#, + ), + ]; + + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_add_bold_italic_delete() { + let ops = vec![ + Insert(0, "123456789", 0), + Bold(0, Interval::new(0, 5), true), + Italic(0, Interval::new(0, 2), true), + Italic(0, Interval::new(2, 4), true), + Bold(0, Interval::new(7, 9), true), + AssertOpsJson( + 0, + r#"[{"insert":"1234","attributes":{"bold":"true","italic":"true"}},{"insert":"5","attributes":{"bold":"true"}},{"insert":"67"},{"insert":"89","attributes":{"bold":"true"}}]"#, + ), + Delete(0, Interval::new(0, 5)), + AssertOpsJson( + 0, + r#"[{"insert":"67","attributes":{"bold":"true"}},{"insert":"89"}]"#, + ), + ]; + + MergeTest::new().run_script(ops); +} + #[test] fn delta_merge_inserted_text_with_same_attribute() { let ops = vec![ @@ -125,21 +223,6 @@ fn delta_merge_inserted_text_with_same_attribute() { MergeTest::new().run_script(ops); } -#[test] -fn delta_compose_attr_delta_with_no_attr_delta_test() { - let expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#; - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), - Insert(1, "7"), - AssertOpsJson(1, r#"[{"insert":"7"}]"#), - Transform(0, 1), - AssertOpsJson(0, expected), - AssertOpsJson(1, expected), - ]; - MergeTest::new().run_script(ops); -} - #[test] fn delta_compose_attr_delta_with_attr_delta_test() { let ops = vec![ @@ -155,6 +238,48 @@ fn delta_compose_attr_delta_with_attr_delta_test() { MergeTest::new().run_script(ops); } +#[test] +fn delta_compose_attr_delta_with_attr_delta_test2() { + let ops = vec![ + Insert(0, "123456", 0), + Bold(0, Interval::new(0, 6), true), + Italic(0, Interval::new(0, 2), true), + Italic(0, Interval::new(4, 6), true), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"bold":"true","italic":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"true"}}]"#, + ), + InsertBold(1, "7", Interval::new(0, 1)), + AssertOpsJson(1, r#"[{"insert":"7","attributes":{"bold":"true"}}]"#), + Transform(0, 1), + AssertOpsJson( + 0, + r#"[{"insert":"12","attributes":{"italic":"true","bold":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"true"}},{"insert":"7","attributes":{"bold":"true"}}]"#, + ), + AssertOpsJson( + 1, + r#"[{"insert":"12","attributes":{"italic":"true","bold":"true"}},{"insert":"34","attributes":{"bold":"true"}},{"insert":"56","attributes":{"italic":"true"}},{"insert":"7","attributes":{"bold":"true"}}]"#, + ), + ]; + + MergeTest::new().run_script(ops); +} + +#[test] +fn delta_compose_attr_delta_with_no_attr_delta_test() { + let expected = r#"[{"insert":"123456","attributes":{"bold":"true"}},{"insert":"7"}]"#; + let ops = vec![ + InsertBold(0, "123456", Interval::new(0, 6)), + AssertOpsJson(0, r#"[{"insert":"123456","attributes":{"bold":"true"}}]"#), + Insert(1, "7", 0), + AssertOpsJson(1, r#"[{"insert":"7"}]"#), + Transform(0, 1), + AssertOpsJson(0, expected), + AssertOpsJson(1, expected), + ]; + MergeTest::new().run_script(ops); +} + #[test] fn delta_delete_heading() { let ops = vec![ diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index f87fde3168..f1319fd418 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -9,7 +9,7 @@ use std::sync::Once; #[derive(Clone, Debug)] pub enum MergeTestOp { - Insert(usize, &'static str), + Insert(usize, &'static str, usize), // delta_i, s, start, length, InsertBold(usize, &'static str, Interval), // delta_i, start, length, enable @@ -43,9 +43,8 @@ impl MergeTest { pub fn run_op(&mut self, op: &MergeTestOp) { match op { - MergeTestOp::Insert(delta_i, s) => { - let delta = &mut self.deltas[*delta_i]; - delta.insert(s, Attributes::Follow); + MergeTestOp::Insert(delta_i, s, index) => { + self.update_delta_with_insert(*delta_i, s, *index); }, MergeTestOp::Delete(delta_i, interval) => { // @@ -88,8 +87,8 @@ impl MergeTest { let target_delta: Delta = serde_json::from_str(&delta_i_json).unwrap(); if expected_delta != target_delta { - log::error!("✅ {}", expected); - log::error!("❌ {}", delta_i_json); + log::error!("✅ expect: {}", expected,); + log::error!("❌ receive: {}", delta_i_json); } assert_eq!(target_delta, expected_delta); }, @@ -102,6 +101,43 @@ impl MergeTest { } } + pub fn update_delta_with_insert(&mut self, delta_index: usize, s: &str, index: usize) { + let old_delta = &mut self.deltas[delta_index]; + let target_interval = Interval::new(0, old_delta.target_len); + if old_delta.target_len < index { + log::error!("{} out of bounds {}", index, target_interval); + } + + let mut attributes = attributes_in_delta(old_delta, &Interval::new(index, index + 1)); + if attributes == Attributes::Empty { + attributes = Attributes::Follow; + } + let insert = OpBuilder::insert(s).attributes(attributes).build(); + + let mut new_delta = Delta::default(); + let prefix = Interval::new(0, index); + let suffix = Interval::new(index, old_delta.target_len); + + split_interval_with_delta(old_delta, &prefix) + .into_iter() + .for_each(|interval| { + let attrs = attributes_in_delta(old_delta, &interval); + new_delta.retain(interval.size() as u64, attrs); + }); + + new_delta.add(insert); + + split_interval_with_delta(old_delta, &suffix) + .into_iter() + .for_each(|interval| { + let attrs = attributes_in_delta(old_delta, &interval); + new_delta.retain(interval.size() as u64, attrs); + }); + + new_delta = old_delta.compose(&new_delta).unwrap(); + self.deltas[delta_index] = new_delta; + } + pub fn update_delta_with_attribute( &mut self, delta_index: usize, @@ -109,58 +145,93 @@ impl MergeTest { interval: &Interval, ) { let old_delta = &self.deltas[delta_index]; - let retain = OpBuilder::retain(interval.size() as u64) + let mut retain = OpBuilder::retain(interval.size() as u64) .attributes(attributes) .build(); - let new_delta = make_delta_with_op(old_delta, retain, interval); + + let attrs = attributes_in_delta(old_delta, &interval); + retain.extend_attributes(attrs); + + let new_delta = new_delta_with_op(old_delta, retain, interval); self.deltas[delta_index] = new_delta; } pub fn update_delta_with_delete(&mut self, delta_index: usize, interval: &Interval) { let old_delta = &self.deltas[delta_index]; - let delete = OpBuilder::delete(interval.size() as u64).build(); - let new_delta = make_delta_with_op(old_delta, delete, interval); + let mut delete = OpBuilder::delete(interval.size() as u64).build(); + let attrs = attributes_in_delta(old_delta, &interval); + delete.extend_attributes(attrs); + + let new_delta = new_delta_with_op(old_delta, delete, interval); self.deltas[delta_index] = new_delta; } } -pub fn make_delta_with_op(delta: &Delta, op: Operation, interval: &Interval) -> Delta { +fn new_delta_with_op(delta: &Delta, op: Operation, interval: &Interval) -> Delta { let mut new_delta = Delta::default(); - let (prefix, suffix) = length_split_with_interval(delta.target_len, interval); + let (prefix, interval, suffix) = target_length_split_with_interval(delta.target_len, *interval); // prefix - if prefix.is_empty() == false && prefix != *interval { - let size = prefix.size(); - let attrs = attributes_in_interval(delta, &prefix); - new_delta.retain(size as u64, attrs); + if prefix.is_empty() == false && prefix != interval { + let intervals = split_interval_with_delta(delta, &prefix); + intervals.into_iter().for_each(|interval| { + let attrs = attributes_in_delta(delta, &interval); + new_delta.retain(interval.size() as u64, attrs); + }); } new_delta.add(op); // suffix if suffix.is_empty() == false { - let size = suffix.size(); - let attrs = attributes_in_interval(delta, &suffix); - new_delta.retain(size as u64, attrs); + let intervals = split_interval_with_delta(delta, &suffix); + intervals.into_iter().for_each(|interval| { + let attrs = attributes_in_delta(delta, &interval); + new_delta.retain(interval.size() as u64, attrs); + }); } delta.compose(&new_delta).unwrap() } -pub fn length_split_with_interval(length: usize, interval: &Interval) -> (Interval, Interval) { +fn split_interval_with_delta(delta: &Delta, interval: &Interval) -> Vec { + let mut start = 0; + let mut new_intervals = vec![]; + delta.ops.iter().for_each(|op| match op { + Operation::Delete(_) => {}, + Operation::Retain(_) => {}, + Operation::Insert(insert) => { + let len = insert.num_chars() as usize; + let end = start + len; + let insert_interval = Interval::new(start, end); + let new_interval = interval.intersect(insert_interval); + + if !new_interval.is_empty() { + new_intervals.push(new_interval) + } + start += len; + }, + }); + new_intervals +} + +pub fn target_length_split_with_interval( + length: usize, + interval: Interval, +) -> (Interval, Interval, Interval) { let original_interval = Interval::new(0, length); - let prefix = original_interval.prefix(*interval); - let suffix = original_interval.suffix(*interval); - (prefix, suffix) + let prefix = original_interval.prefix(interval); + let suffix = original_interval.suffix(interval); + (prefix, interval, suffix) } pub fn debug_print_delta(delta: &Delta) { log::debug!("😁 {}", serde_json::to_string(delta).unwrap()); } -pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes { +pub fn attributes_in_delta(delta: &Delta, interval: &Interval) -> Attributes { let mut attributes_data = AttributesData::new(); - let mut offset = 0; + let mut offset: usize = 0; delta.ops.iter().for_each(|op| match op { Operation::Delete(_n) => {}, @@ -178,10 +249,14 @@ pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes Operation::Insert(insert) => match &insert.attributes { Attributes::Follow => {}, Attributes::Custom(data) => { - if interval.start >= offset && insert.num_chars() > (interval.end as u64 - 1) { + let end = insert.num_chars() as usize; + if !interval + .intersect(Interval::new(offset, offset + end)) + .is_empty() + { attributes_data.extend(data.clone()); } - offset += insert.num_chars() as usize; + offset += end; }, Attributes::Empty => {}, }, @@ -193,6 +268,13 @@ pub fn attributes_in_interval(delta: &Delta, interval: &Interval) -> Attributes Attributes::Custom(attributes_data) } } +fn attributes_in_operation(op: &Operation, interval: &Interval) -> Attributes { + match op { + Operation::Delete(_) => Attributes::Empty, + Operation::Retain(retain) => Attributes::Empty, + Operation::Insert(insert) => Attributes::Empty, + } +} pub struct Rng(StdRng); diff --git a/rust-lib/flowy-ot/tests/op_test.rs b/rust-lib/flowy-ot/tests/op_test.rs index 7c456739ab..a931b0e264 100644 --- a/rust-lib/flowy-ot/tests/op_test.rs +++ b/rust-lib/flowy-ot/tests/op_test.rs @@ -2,7 +2,7 @@ pub mod helper; use crate::helper::MergeTestOp::*; use bytecount::num_chars; -use flowy_ot::{attributes::*, delta::Delta, operation::OpBuilder}; +use flowy_ot::{attributes::*, delta::Delta, interval::Interval, operation::OpBuilder}; use helper::*; #[test] @@ -188,8 +188,8 @@ fn transform() { #[test] fn transform2() { let ops = vec![ - Insert(0, "123"), - Insert(1, "456"), + Insert(0, "123", 0), + Insert(1, "456", 0), Transform(0, 1), AssertStr(0, "123456"), AssertStr(1, "123456"),