add auto exit block test

This commit is contained in:
appflowy 2021-08-17 11:23:28 +08:00
parent 9bc72d3b9e
commit 4ab4f744ba
20 changed files with 282 additions and 164 deletions

View File

@ -73,6 +73,7 @@ class Document {
print('insert delta: $delta');
compose(delta, ChangeSource.LOCAL);
print('compose insert, current document $_delta');
print('compose end');
return delta;
}

View File

@ -13,10 +13,8 @@ impl FormatExt for FormatLinkAtCaretPositionExt {
return None;
}
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(interval.start);
let (before, after) = (iter.next_op_before(interval.size()), iter.next());
let mut iter = DeltaIter::from_offset(delta, interval.start);
let (before, after) = (iter.last_op_before_index(interval.size()), iter.next_op());
let mut start = interval.end;
let mut retain = 0;

View File

@ -25,12 +25,11 @@ impl FormatExt for ResolveBlockFormatExt {
}
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(interval.start);
let mut iter = DeltaIter::from_offset(delta, interval.start);
let mut start = 0;
let end = interval.size();
while start < end && iter.has_next() {
let next_op = iter.next_op_before(end - start).unwrap();
let next_op = iter.last_op_before_index(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.len(), Attributes::empty()),
Some(_) => {

View File

@ -15,14 +15,12 @@ impl FormatExt for ResolveInlineFormatExt {
return None;
}
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(interval.start);
let mut iter = DeltaIter::from_offset(delta, interval.start);
let mut start = 0;
let end = interval.size();
while start < end && iter.has_next() {
let next_op = iter.next_op_before(end - start).unwrap();
let next_op = iter.last_op_before_index(end - start).unwrap();
match find_newline(next_op.get_data()) {
None => new_delta.retain(next_op.len(), attribute.clone().into()),
Some(_) => {

View File

@ -1,23 +1,70 @@
use crate::{
client::{extensions::InsertExt, util::is_newline},
core::{Delta, DeltaIter},
core::{
AttributeKey,
AttributeValue,
Attributes,
CharMetric,
Delta,
DeltaBuilder,
DeltaIter,
Operation,
},
};
use crate::core::is_empty_line_at_index;
pub struct AutoExitBlockExt {}
impl InsertExt for AutoExitBlockExt {
fn ext_name(&self) -> &str { "AutoExitBlockExt" }
fn apply(&self, delta: &Delta, _replace_len: usize, text: &str, index: usize) -> Option<Delta> {
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
// Auto exit block will be triggered by enter two new lines
if !is_newline(text) {
return None;
}
let mut iter = DeltaIter::new(delta);
let _prev = iter.next_op_before(index);
let _next = iter.next_op();
if !is_empty_line_at_index(delta, index) {
return None;
}
None
let mut iter = DeltaIter::from_offset(delta, index);
let next = iter.next_op()?;
let mut attributes = next.get_attributes();
let block_attributes = attributes_except_header(&next);
if block_attributes.is_empty() {
return None;
}
if next.len() > 1 {
return None;
}
match iter.first_op_contains_newline() {
None => {},
Some((newline_op, _)) => {
let newline_attributes = attributes_except_header(&newline_op);
if block_attributes == newline_attributes {
return None;
}
},
}
attributes.mark_as_removed_except(&AttributeKey::Header);
Some(
DeltaBuilder::new()
.retain(index + replace_len)
.retain_with_attributes(1, attributes)
.build(),
)
}
}
fn attributes_except_header(op: &Operation) -> Attributes {
let mut attributes = op.get_attributes();
attributes.remove(AttributeKey::Header);
attributes
}

View File

@ -13,7 +13,7 @@ impl InsertExt for AutoFormatExt {
return None;
}
let mut iter = DeltaIter::new(delta);
if let Some(prev) = iter.next_op_before(index) {
if let Some(prev) = iter.last_op_before_index(index) {
match AutoFormat::parse(prev.get_data()) {
None => {},
Some(formatter) => {

View File

@ -1,6 +1,6 @@
use crate::{
client::extensions::{InsertExt, NEW_LINE},
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter},
client::extensions::InsertExt,
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter, NEW_LINE},
};
pub struct DefaultInsertExt {}

View File

@ -1,9 +1,6 @@
use crate::{
client::{
extensions::InsertExt,
util::{contain_newline, OpNewline},
},
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter},
client::{extensions::InsertExt, util::contain_newline},
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter, OpNewline},
};
pub struct PreserveInlineStylesExt {}
@ -16,7 +13,7 @@ impl InsertExt for PreserveInlineStylesExt {
}
let mut iter = DeltaIter::new(delta);
let prev = iter.next_op_before(index)?;
let prev = iter.last_op_before_index(index)?;
if OpNewline::parse(&prev).is_contain() {
return None;
}

View File

@ -1,9 +1,6 @@
use crate::{
client::{
extensions::{InsertExt, NEW_LINE},
util::is_newline,
},
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
client::{extensions::InsertExt, util::is_newline},
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter, NEW_LINE},
};
pub struct ResetLineFormatOnNewLineExt {}
@ -16,8 +13,7 @@ impl InsertExt for ResetLineFormatOnNewLineExt {
}
let mut iter = DeltaIter::new(delta);
iter.seek::<CharMetric>(index);
let next_op = iter.next()?;
let next_op = iter.first_op_after_index(index)?;
if !next_op.get_data().starts_with(NEW_LINE) {
return None;
}

View File

@ -8,9 +8,6 @@ mod delete;
mod format;
mod insert;
pub const NEW_LINE: &'static str = "\n";
pub const WHITESPACE: &'static str = " ";
pub type InsertExtension = Box<dyn InsertExt>;
pub type FormatExtension = Box<dyn FormatExt>;
pub type DeleteExtension = Box<dyn DeleteExt>;

View File

@ -1,7 +1,4 @@
use crate::{
client::extensions::{NEW_LINE, WHITESPACE},
core::Operation,
};
use crate::core::{Operation, NEW_LINE, WHITESPACE};
#[inline]
pub fn find_newline(s: &str) -> Option<usize> {
@ -11,51 +8,6 @@ pub fn find_newline(s: &str) -> Option<usize> {
}
}
#[derive(PartialEq, Eq)]
pub enum OpNewline {
Start,
End,
Contain,
Equal,
NotFound,
}
impl OpNewline {
pub fn parse(op: &Operation) -> OpNewline {
let s = op.get_data();
if s == NEW_LINE {
return OpNewline::Equal;
}
if s.starts_with(NEW_LINE) {
return OpNewline::Start;
}
if s.ends_with(NEW_LINE) {
return OpNewline::End;
}
if s.contains(NEW_LINE) {
return OpNewline::Contain;
}
OpNewline::NotFound
}
pub fn is_start(&self) -> bool { self == &OpNewline::Start }
pub fn is_end(&self) -> bool { self == &OpNewline::End }
pub fn is_not_found(&self) -> bool { self == &OpNewline::NotFound }
pub fn is_contain(&self) -> bool {
self.is_start() || self.is_end() || self.is_equal() || self == &OpNewline::Contain
}
pub fn is_equal(&self) -> bool { self == &OpNewline::Equal }
}
#[inline]
pub fn is_op_contains_newline(op: &Operation) -> bool { contain_newline(op.get_data()) }

View File

@ -6,11 +6,19 @@ use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
lazy_static! {
static ref BLOCK_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
AttributeKey::Header,
AttributeKey::Align,
AttributeKey::List,
AttributeKey::CodeBlock,
AttributeKey::QuoteBlock,
AttributeKey::LeftAlignment,
AttributeKey::CenterAlignment,
AttributeKey::RightAlignment,
AttributeKey::JustifyAlignment,
AttributeKey::Indent,
AttributeKey::Align,
AttributeKey::CodeBlock,
AttributeKey::List,
AttributeKey::Bullet,
AttributeKey::Ordered,
AttributeKey::Checked,
AttributeKey::UnChecked,
AttributeKey::QuoteBlock,
]);
static ref INLINE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
AttributeKey::Bold,
@ -19,8 +27,15 @@ lazy_static! {
AttributeKey::StrikeThrough,
AttributeKey::Link,
AttributeKey::Color,
AttributeKey::Font,
AttributeKey::Size,
AttributeKey::Background,
]);
static ref INGORE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
AttributeKey::Width,
AttributeKey::Height,
AttributeKey::Style,
]);
}
#[derive(Debug, PartialEq, Eq, Clone)]
@ -116,45 +131,26 @@ impl AttributeKey {
pub fn value<T: Into<AttributeValue>>(&self, value: T) -> Attribute {
let key = self.clone();
let value: AttributeValue = value.into();
match self {
AttributeKey::Bold
| AttributeKey::Italic
| AttributeKey::Underline
| AttributeKey::StrikeThrough
| AttributeKey::Link
| AttributeKey::Color
| AttributeKey::Background
| AttributeKey::Font
| AttributeKey::Size => Attribute {
if INLINE_KEYS.contains(self) {
return Attribute {
key,
value,
scope: AttributeScope::Inline,
},
};
}
AttributeKey::Header
| AttributeKey::LeftAlignment
| AttributeKey::CenterAlignment
| AttributeKey::RightAlignment
| AttributeKey::JustifyAlignment
| AttributeKey::Indent
| AttributeKey::Align
| AttributeKey::CodeBlock
| AttributeKey::List
| AttributeKey::Bullet
| AttributeKey::Ordered
| AttributeKey::Checked
| AttributeKey::UnChecked
| AttributeKey::QuoteBlock => Attribute {
if BLOCK_KEYS.contains(self) {
return Attribute {
key,
value,
scope: AttributeScope::Block,
},
};
}
AttributeKey::Width | AttributeKey::Height | AttributeKey::Style => Attribute {
key,
value,
scope: AttributeScope::Ignore,
},
Attribute {
key,
value,
scope: AttributeScope::Ignore,
}
}
}
@ -183,3 +179,10 @@ impl std::convert::From<bool> for AttributeValue {
AttributeValue(val.to_owned())
}
}
pub fn is_block_except_header(k: &AttributeKey) -> bool {
if k == &AttributeKey::Header {
return false;
}
BLOCK_KEYS.contains(k)
}

View File

@ -37,17 +37,39 @@ impl Attributes {
self.inner.insert(key, value);
}
pub fn remove(&mut self, key: &AttributeKey) {
pub fn mark_as_removed(&mut self, key: &AttributeKey) {
let value: AttributeValue = REMOVE_FLAG.into();
self.inner.insert(key.clone(), value);
}
// Remove the key if its value is empty. e.g. { bold: "" }
pub fn remove_empty_value(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
pub fn mark_as_removed_except(&mut self, attribute: &AttributeKey) {
self.inner.iter_mut().for_each(|(k, v)| {
if k != attribute {
v.0 = REMOVE_FLAG.into();
}
v.0 = REMOVE_FLAG.into();
});
}
pub fn remove(&mut self, key: AttributeKey) { self.inner.retain(|k, _| k != &key); }
// pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
// { let mut new_attributes = Attributes::new();
// attributes.iter().for_each(|(k, v)| {
// if k != &AttributeKey::Header {
// new_attributes.insert(k.clone(), v.clone());
// }
// });
//
// new_attributes
// }
// Remove the empty attribute which value is empty. e.g. {bold: ""}.
pub fn remove_empty(&mut self) { self.inner.retain(|_, v| !should_remove(v)); }
pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
// Update self attributes by constructing new attributes from the other if it's
// Update inner by constructing new attributes from the other if it's
// not None and replace the key/value with self key/value.
pub fn merge(&mut self, other: Option<Attributes>) {
if other.is_none() {
@ -144,7 +166,7 @@ pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes {
let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| {
if base.get(k) != attr.get(k) && !base.contains_key(k) {
attributes.remove(k);
attributes.mark_as_removed(k);
}
attributes
});
@ -156,15 +178,3 @@ pub fn merge_attributes(mut attributes: Attributes, other: Attributes) -> Attrib
attributes.extend(other);
attributes
}
pub trait AttributesRule {
// Remove the empty attribute that its value is empty. e.g. {bold: ""}.
fn remove_empty(self) -> Attributes;
}
impl AttributesRule for Attributes {
fn remove_empty(mut self) -> Attributes {
self.remove_empty_value();
self.into()
}
}

View File

@ -34,10 +34,10 @@ impl<'a> Cursor<'a> {
// get the next operation interval
pub fn next_iv(&self) -> Interval { self.next_iv_before(None) }
pub fn next_op(&mut self) -> Option<Operation> { self.next_op_before(None) }
pub fn next_op(&mut self) -> Option<Operation> { self.last_op_before_index(None) }
// get the last operation before the index
pub fn next_op_before(&mut self, index: Option<usize>) -> Option<Operation> {
pub fn last_op_before_index(&mut self, index: Option<usize>) -> Option<Operation> {
let mut find_op = None;
let next_op = self.next_op.take();
let mut next_op = next_op.as_ref();
@ -74,7 +74,7 @@ impl<'a> Cursor<'a> {
let pos = self.cur_char_count - pre_char_count;
let end = index.unwrap();
if end > pos {
return self.next_op_before(Some(end - pos));
return self.last_op_before_index(Some(end - pos));
}
}
return find_op;
@ -173,7 +173,7 @@ pub struct CharMetric {}
impl Metric for CharMetric {
fn seek(cursor: &mut Cursor, index: usize) -> SeekResult {
let _ = check_bound(cursor.cur_char_count, index)?;
let _ = cursor.next_op_before(Some(index));
let _ = cursor.last_op_before_index(Some(index));
Ok(())
}

View File

@ -168,11 +168,11 @@ impl Delta {
);
let op = iter
.next_op_before(length)
.last_op_before_index(length)
.unwrap_or(OpBuilder::retain(length).build());
let other_op = other_iter
.next_op_before(length)
.last_op_before_index(length)
.unwrap_or(OpBuilder::retain(length).build());
debug_assert_eq!(op.len(), other_op.len());
@ -194,7 +194,7 @@ impl Delta {
insert.attributes.clone(),
other_retain.attributes.clone(),
);
composed_attrs = composed_attrs.remove_empty();
composed_attrs.remove_empty();
new_delta.add(
OpBuilder::insert(op.get_data())
.attributes(composed_attrs)
@ -300,7 +300,7 @@ impl Delta {
},
(Some(Operation::Insert(insert)), Some(Operation::Retain(o_retain))) => {
let mut composed_attrs = compose_operation(&next_op1, &next_op2);
composed_attrs = composed_attrs.remove_empty();
composed_attrs.remove_empty();
log::debug!(
"compose: [{} - {}], composed_attrs: {}",

View File

@ -1,5 +1,5 @@
use super::cursor::*;
use crate::core::{Attributes, Delta, Interval, Operation};
use crate::core::{Attributes, Delta, Interval, Operation, NEW_LINE};
use std::ops::{Deref, DerefMut};
pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;
@ -14,6 +14,13 @@ impl<'a> DeltaIter<'a> {
Self::from_interval(delta, interval)
}
pub fn from_offset(delta: &'a Delta, offset: usize) -> Self {
let interval = Interval::new(0, MAX_IV_LEN);
let mut iter = Self::from_interval(delta, interval);
iter.seek::<CharMetric>(offset);
iter
}
pub fn from_interval(delta: &'a Delta, interval: Interval) -> Self {
let cursor = Cursor::new(delta, interval);
Self { cursor }
@ -21,8 +28,6 @@ impl<'a> DeltaIter<'a> {
pub fn ops(&mut self) -> Vec<Operation> { self.collect::<Vec<_>>() }
pub fn next_op(&mut self) -> Option<Operation> { self.cursor.next_op() }
pub fn next_op_len(&self) -> Option<usize> {
let interval = self.cursor.next_iv();
if interval.is_empty() {
@ -32,8 +37,30 @@ impl<'a> DeltaIter<'a> {
}
}
pub fn next_op_before(&mut self, index: usize) -> Option<Operation> {
self.cursor.next_op_before(Some(index))
// find next op contains NEW_LINE
pub fn first_op_contains_newline(&mut self) -> Option<(Operation, usize)> {
let mut offset = 0;
while self.has_next() {
if let Some(op) = self.next_op() {
if OpNewline::parse(&op).is_contain() {
return Some((op, offset));
}
offset += op.len();
}
}
None
}
pub fn next_op(&mut self) -> Option<Operation> { self.cursor.next_op() }
pub fn last_op_before_index(&mut self, index: usize) -> Option<Operation> {
self.cursor.last_op_before_index(Some(index))
}
pub fn first_op_after_index(&mut self, index: usize) -> Option<Operation> {
self.seek::<CharMetric>(index);
self.next_op()
}
pub fn seek<M: Metric>(&mut self, index: usize) {
@ -72,6 +99,22 @@ impl<'a> Iterator for DeltaIter<'a> {
fn next(&mut self) -> Option<Self::Item> { self.next_op() }
}
pub fn is_empty_line_at_index(delta: &Delta, index: usize) -> bool {
let mut iter = DeltaIter::new(delta);
let (prev, next) = (iter.last_op_before_index(index), iter.next_op());
if prev.is_none() {
return true;
}
if next.is_none() {
return false;
}
let prev = prev.unwrap();
let next = next.unwrap();
OpNewline::parse(&prev).is_end() && OpNewline::parse(&next).is_start()
}
pub struct AttributesIter<'a> {
delta_iter: DeltaIter<'a>,
}
@ -133,3 +176,48 @@ impl<'a> Iterator for AttributesIter<'a> {
Some((length, attributes))
}
}
#[derive(PartialEq, Eq)]
pub enum OpNewline {
Start,
End,
Contain,
Equal,
NotFound,
}
impl OpNewline {
pub fn parse(op: &Operation) -> OpNewline {
let s = op.get_data();
if s == NEW_LINE {
return OpNewline::Equal;
}
if s.starts_with(NEW_LINE) {
return OpNewline::Start;
}
if s.ends_with(NEW_LINE) {
return OpNewline::End;
}
if s.contains(NEW_LINE) {
return OpNewline::Contain;
}
OpNewline::NotFound
}
pub fn is_start(&self) -> bool { self == &OpNewline::Start || self.is_equal() }
pub fn is_end(&self) -> bool { self == &OpNewline::End || self.is_equal() }
pub fn is_not_found(&self) -> bool { self == &OpNewline::NotFound }
pub fn is_contain(&self) -> bool {
self.is_start() || self.is_end() || self.is_equal() || self == &OpNewline::Contain
}
pub fn is_equal(&self) -> bool { self == &OpNewline::Equal }
}

View File

@ -8,3 +8,6 @@ pub use builder::*;
pub use cursor::*;
pub use delta::*;
pub use iterator::*;
pub const NEW_LINE: &'static str = "\n";
pub const WHITESPACE: &'static str = " ";

View File

@ -3,7 +3,7 @@ pub mod helper;
use crate::helper::{TestOp::*, *};
use flowy_ot::core::Interval;
use flowy_ot::client::extensions::{NEW_LINE, WHITESPACE};
use flowy_ot::core::{NEW_LINE, WHITESPACE};
#[test]
fn attributes_insert_text() {
@ -646,6 +646,10 @@ fn attributes_add_bullet() {
r#"[{"insert":"1"},{"insert":"\n","attributes":{"bullet":"true"}}]"#,
),
Insert(0, NEW_LINE, 1),
AssertOpsJson(
0,
r#"[{"insert":"1"},{"insert":"\n\n","attributes":{"bullet":"true"}}]"#,
),
Insert(0, "2", 2),
AssertOpsJson(
0,
@ -672,3 +676,19 @@ fn attributes_un_bullet_one() {
OpTester::new().run_script_with_newline(ops);
}
#[test]
fn attributes_auto_exit_block() {
let ops = vec![
Insert(0, "1", 0),
Bullet(0, Interval::new(0, 1), true),
Insert(0, NEW_LINE, 1),
Insert(0, NEW_LINE, 2),
AssertOpsJson(
0,
r#"[{"insert":"1"},{"insert":"\n","attributes":{"bullet":"true"}},{"insert":"\n"}]"#,
),
];
OpTester::new().run_script_with_newline(ops);
}

View File

@ -3,7 +3,7 @@ use flowy_ot::{client::Document, core::*};
use rand::{prelude::*, Rng as WrappedRng};
use std::{sync::Once, time::Duration};
const LEVEL: &'static str = "info";
const LEVEL: &'static str = "debug";
#[derive(Clone, Debug, Display)]
pub enum TestOp {

View File

@ -147,14 +147,13 @@ fn delta_get_ops_in_interval_7() {
delta.add(insert_a.clone());
delta.add(retain_a.clone());
let mut iter_1 = DeltaIter::new(&delta);
iter_1.seek::<CharMetric>(2);
let mut iter_1 = DeltaIter::from_offset(&delta, 2);
assert_eq!(iter_1.next_op().unwrap(), OpBuilder::insert("345").build());
assert_eq!(iter_1.next_op().unwrap(), OpBuilder::retain(3).build());
let mut iter_2 = DeltaIter::new(&delta);
assert_eq!(
iter_2.next_op_before(2).unwrap(),
iter_2.last_op_before_index(2).unwrap(),
OpBuilder::insert("12").build()
);
assert_eq!(iter_2.next_op().unwrap(), OpBuilder::insert("345").build());
@ -181,7 +180,7 @@ fn delta_seek_2() {
let mut iter = DeltaIter::new(&delta);
assert_eq!(
iter.next_op_before(1).unwrap(),
iter.last_op_before_index(1).unwrap(),
OpBuilder::insert("1").build()
);
}
@ -193,21 +192,21 @@ fn delta_seek_3() {
let mut iter = DeltaIter::new(&delta);
assert_eq!(
iter.next_op_before(2).unwrap(),
iter.last_op_before_index(2).unwrap(),
OpBuilder::insert("12").build()
);
assert_eq!(
iter.next_op_before(2).unwrap(),
iter.last_op_before_index(2).unwrap(),
OpBuilder::insert("34").build()
);
assert_eq!(
iter.next_op_before(2).unwrap(),
iter.last_op_before_index(2).unwrap(),
OpBuilder::insert("5").build()
);
assert_eq!(iter.next_op_before(1), None);
assert_eq!(iter.last_op_before_index(1), None);
}
#[test]
@ -218,7 +217,7 @@ fn delta_seek_4() {
let mut iter = DeltaIter::new(&delta);
iter.seek::<CharMetric>(3);
assert_eq!(
iter.next_op_before(2).unwrap(),
iter.last_op_before_index(2).unwrap(),
OpBuilder::insert("45").build()
);
}
@ -238,7 +237,7 @@ fn delta_seek_5() {
iter.seek::<CharMetric>(0);
assert_eq!(
iter.next_op_before(4).unwrap(),
iter.last_op_before_index(4).unwrap(),
OpBuilder::insert("1234").attributes(attributes).build(),
);
}
@ -252,7 +251,7 @@ fn delta_next_op_len_test() {
iter.seek::<CharMetric>(3);
assert_eq!(iter.next_op_len().unwrap(), 2);
assert_eq!(
iter.next_op_before(1).unwrap(),
iter.last_op_before_index(1).unwrap(),
OpBuilder::insert("4").build()
);
assert_eq!(iter.next_op_len().unwrap(), 1);
@ -267,12 +266,22 @@ fn delta_next_op_len_test2() {
assert_eq!(iter.next_op_len().unwrap(), 5);
assert_eq!(
iter.next_op_before(5).unwrap(),
iter.last_op_before_index(5).unwrap(),
OpBuilder::insert("12345").build()
);
assert_eq!(iter.next_op_len(), None);
}
#[test]
fn delta_next_op_len_test3() {
let mut delta = Delta::default();
delta.add(OpBuilder::insert("12345").build());
let mut iter = DeltaIter::new(&delta);
assert_eq!(iter.last_op_before_index(0), None,);
assert_eq!(iter.next_op_len().unwrap(), 5);
}
#[test]
fn lengths() {
let mut delta = Delta::default();