mod core;
mod group;
mod merge;
mod query;
#[cfg(test)]
mod tests {
use crate::utils::undo::{GroupedUndoHistory, Mergeable, UndoGroup, UndoHistory};
#[derive(Clone, Debug, PartialEq)]
enum TestOp {
Insert { pos: usize, text: String },
Delete { pos: usize, text: String },
}
impl Mergeable for TestOp {
fn can_merge(&self, other: &Self) -> bool {
match (self, other) {
(TestOp::Insert { pos: p1, text: t1 }, TestOp::Insert { pos: p2, .. }) => {
*p2 == *p1 + t1.chars().count()
}
_ => false,
}
}
fn merge(self, other: Self) -> Self {
match (self, other) {
(TestOp::Insert { pos, text: mut t1 }, TestOp::Insert { text: t2, .. }) => {
t1.push_str(&t2);
TestOp::Insert { pos, text: t1 }
}
(op, _) => op,
}
}
}
#[test]
fn test_new() {
let history: UndoHistory<TestOp> = UndoHistory::new();
assert!(!history.can_undo());
assert!(!history.can_redo());
assert_eq!(history.undo_count(), 0);
assert_eq!(history.redo_count(), 0);
}
#[test]
fn test_push() {
let mut history = UndoHistory::new();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
assert!(history.can_undo());
assert!(!history.can_redo());
assert_eq!(history.undo_count(), 1);
}
#[test]
fn test_undo() {
let mut history = UndoHistory::new();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
let op = history.undo();
assert_eq!(
op,
Some(TestOp::Insert {
pos: 1,
text: "b".into()
})
);
assert!(history.can_undo());
assert!(history.can_redo());
let op = history.undo();
assert_eq!(
op,
Some(TestOp::Insert {
pos: 0,
text: "a".into()
})
);
assert!(!history.can_undo());
assert!(history.can_redo());
}
#[test]
fn test_redo() {
let mut history = UndoHistory::new();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
history.undo();
history.undo();
let op = history.redo();
assert_eq!(
op,
Some(TestOp::Insert {
pos: 0,
text: "a".into()
})
);
let op = history.redo();
assert_eq!(
op,
Some(TestOp::Insert {
pos: 1,
text: "b".into()
})
);
assert!(!history.can_redo());
}
#[test]
fn test_undo_clears_redo_on_push() {
let mut history = UndoHistory::new();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
history.undo();
assert!(history.can_redo());
history.push(TestOp::Insert {
pos: 1,
text: "c".into(),
});
assert!(!history.can_redo());
}
#[test]
fn test_max_size() {
let mut history: UndoHistory<TestOp> = UndoHistory::with_max_size(3);
for i in 0..5 {
history.push(TestOp::Insert {
pos: i,
text: i.to_string(),
});
}
assert_eq!(history.undo_count(), 3);
let op = history.undo();
assert_eq!(
op,
Some(TestOp::Insert {
pos: 4,
text: "4".into()
})
);
}
#[test]
fn test_clear() {
let mut history = UndoHistory::new();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.undo();
history.clear();
assert!(!history.can_undo());
assert!(!history.can_redo());
}
#[test]
fn test_peek() {
let mut history = UndoHistory::new();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
assert_eq!(
history.peek_undo(),
Some(&TestOp::Insert {
pos: 1,
text: "b".into()
})
);
history.undo();
assert_eq!(
history.peek_redo(),
Some(&TestOp::Insert {
pos: 1,
text: "b".into()
})
);
}
#[test]
fn test_push_merge() {
let mut history = UndoHistory::new();
history.push_merge(TestOp::Insert {
pos: 0,
text: "H".into(),
});
history.push_merge(TestOp::Insert {
pos: 1,
text: "i".into(),
});
history.push_merge(TestOp::Insert {
pos: 2,
text: "!".into(),
});
assert_eq!(history.undo_count(), 1);
let op = history.undo();
assert_eq!(
op,
Some(TestOp::Insert {
pos: 0,
text: "Hi!".into()
})
);
}
#[test]
fn test_push_merge_different_types() {
let mut history = UndoHistory::new();
history.push_merge(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push_merge(TestOp::Delete {
pos: 0,
text: "a".into(),
});
assert_eq!(history.undo_count(), 2);
}
#[test]
fn test_push_merge_non_consecutive() {
let mut history = UndoHistory::new();
history.push_merge(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push_merge(TestOp::Insert {
pos: 5,
text: "b".into(),
});
assert_eq!(history.undo_count(), 2);
}
#[test]
fn test_grouped_history() {
let mut history = GroupedUndoHistory::new();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
assert!(history.can_undo());
let group = history.undo();
assert!(group.is_some());
assert_eq!(group.unwrap().len(), 1);
}
#[test]
fn test_grouped_history_groups() {
let mut history = GroupedUndoHistory::new();
history.begin_group();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
history.push(TestOp::Insert {
pos: 2,
text: "c".into(),
});
history.end_group();
let group = history.undo().unwrap();
assert_eq!(group.len(), 3);
let ops: Vec<_> = group.iter().collect();
assert_eq!(
ops[0],
&TestOp::Insert {
pos: 0,
text: "a".into()
}
);
assert_eq!(
ops[1],
&TestOp::Insert {
pos: 1,
text: "b".into()
}
);
assert_eq!(
ops[2],
&TestOp::Insert {
pos: 2,
text: "c".into()
}
);
}
#[test]
fn test_grouped_history_nested_begin() {
let mut history = GroupedUndoHistory::new();
history.begin_group();
history.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
history.begin_group();
history.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
history.end_group();
let group = history.undo().unwrap();
assert_eq!(group.len(), 2);
}
#[test]
fn test_undo_group_reversed() {
let group = {
let mut g = UndoGroup::new();
g.push(TestOp::Insert {
pos: 0,
text: "a".into(),
});
g.push(TestOp::Insert {
pos: 1,
text: "b".into(),
});
g.push(TestOp::Insert {
pos: 2,
text: "c".into(),
});
g
};
let reversed = group.reversed();
assert_eq!(
reversed[0],
TestOp::Insert {
pos: 2,
text: "c".into()
}
);
assert_eq!(
reversed[1],
TestOp::Insert {
pos: 1,
text: "b".into()
}
);
assert_eq!(
reversed[2],
TestOp::Insert {
pos: 0,
text: "a".into()
}
);
}
#[test]
fn test_undo_empty() {
let mut history: UndoHistory<TestOp> = UndoHistory::new();
assert_eq!(history.undo(), None);
}
#[test]
fn test_redo_empty() {
let mut history: UndoHistory<TestOp> = UndoHistory::new();
assert_eq!(history.redo(), None);
}
#[test]
fn test_set_max_size() {
let mut history: UndoHistory<TestOp> = UndoHistory::new();
for i in 0..10 {
history.push(TestOp::Insert {
pos: i,
text: i.to_string(),
});
}
assert_eq!(history.undo_count(), 10);
history.set_max_size(5);
assert_eq!(history.undo_count(), 5);
let op = history.undo();
assert_eq!(
op,
Some(TestOp::Insert {
pos: 9,
text: "9".into()
})
);
}
}
mod types;
mod undo_redo;
pub use group::{GroupedUndoHistory, UndoGroup};
pub use types::{Mergeable, UndoHistory, DEFAULT_MAX_HISTORY};