use text_document::{
Alignment, BlockFormat, CharVerticalAlignment, FindOptions, ListStyle, MarkerType, MoveMode,
MoveOperation, SelectionType, TextDocument, TextFormat, UnderlineStyle,
};
fn new_doc(text: &str) -> TextDocument {
let doc = TextDocument::new();
doc.set_plain_text(text).unwrap();
doc
}
#[test]
fn undo_redo_insert_text() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_text(" world").unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
}
#[test]
fn undo_redo_delete_text() {
let doc = new_doc("Hello world");
let c = doc.cursor();
c.move_position(MoveOperation::NextCharacter, MoveMode::KeepAnchor, 5);
c.remove_selected_text().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), " world");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), " world");
}
#[test]
fn undo_redo_insert_block() {
let doc = new_doc("Hello world");
let c = doc.cursor_at(5);
c.insert_block().unwrap();
assert_eq!(doc.block_count(), 2);
doc.undo().unwrap();
assert_eq!(doc.block_count(), 1);
doc.redo().unwrap();
assert_eq!(doc.block_count(), 2);
}
#[test]
fn undo_redo_insert_image() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_image("test.png", "", 100, 50).unwrap();
assert_eq!(doc.stats().image_count, 1);
let char_count_after = doc.character_count();
doc.undo().unwrap();
assert_eq!(doc.stats().image_count, 0);
assert_eq!(doc.character_count(), 5);
doc.redo().unwrap();
assert_eq!(doc.stats().image_count, 1);
assert_eq!(doc.character_count(), char_count_after);
}
#[test]
fn insert_image_into_empty_doc() {
let doc = TextDocument::new();
let c = doc.cursor();
c.insert_image("img.png", "", 200, 100).unwrap();
assert_eq!(doc.stats().image_count, 1);
}
#[test]
fn insert_image_after_image() {
let doc = TextDocument::new();
let c = doc.cursor();
c.insert_image("img1.png", "", 100, 100).unwrap();
c.insert_image("img2.png", "", 200, 200).unwrap();
assert_eq!(doc.stats().image_count, 2);
}
#[test]
fn undo_redo_insert_formatted_text() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
let fmt = TextFormat {
font_bold: Some(true),
font_italic: Some(true),
font_family: Some("Arial".into()),
font_point_size: Some(14),
..Default::default()
};
c.insert_formatted_text(" bold", &fmt).unwrap();
assert!(doc.to_plain_text().unwrap().contains("bold"));
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
doc.redo().unwrap();
assert!(doc.to_plain_text().unwrap().contains("bold"));
}
#[test]
fn undo_redo_insert_frame() {
let doc = new_doc("Hello");
let frames_before = doc.stats().frame_count;
let c = doc.cursor();
c.insert_frame().unwrap();
assert!(doc.stats().frame_count > frames_before);
doc.undo().unwrap();
assert_eq!(doc.stats().frame_count, frames_before);
doc.redo().unwrap();
assert!(doc.stats().frame_count > frames_before);
}
#[test]
fn undo_redo_insert_fragment() {
let source = new_doc("Fragment text");
let frag = text_document::DocumentFragment::from_document(&source).unwrap();
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_fragment(&frag).unwrap();
let after_insert = doc.to_plain_text().unwrap();
assert!(after_insert.contains("Fragment"));
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
doc.redo().unwrap();
let after_redo = doc.to_plain_text().unwrap();
assert!(after_redo.contains("Fragment"));
}
#[test]
fn undo_redo_insert_html() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_html("<b>World</b>").unwrap();
assert!(doc.to_plain_text().unwrap().contains("World"));
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
doc.redo().unwrap();
assert!(doc.to_plain_text().unwrap().contains("World"));
}
#[test]
fn undo_redo_insert_markdown() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_markdown(" **World**").unwrap();
assert!(doc.to_plain_text().unwrap().contains("World"));
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
doc.redo().unwrap();
assert!(doc.to_plain_text().unwrap().contains("World"));
}
#[test]
fn undo_redo_create_list() {
let doc = new_doc("Item 1\nItem 2");
let c = doc.cursor();
c.select(SelectionType::Document);
c.create_list(ListStyle::Disc).unwrap();
assert!(doc.stats().list_count >= 1);
doc.undo().unwrap();
assert_eq!(doc.stats().list_count, 0);
doc.redo().unwrap();
assert!(doc.stats().list_count >= 1);
}
#[test]
fn undo_redo_insert_list() {
let doc = new_doc("Before");
let c = doc.cursor_at(6);
c.insert_list(ListStyle::Decimal).unwrap();
let lists_after = doc.stats().list_count;
assert!(lists_after >= 1);
doc.undo().unwrap();
assert_eq!(doc.stats().list_count, 0);
doc.redo().unwrap();
assert_eq!(doc.stats().list_count, lists_after);
}
#[test]
fn undo_redo_set_text_format() {
let doc = new_doc("Hello world");
let c = doc.cursor();
c.move_position(MoveOperation::NextCharacter, MoveMode::KeepAnchor, 5);
let fmt = TextFormat {
font_bold: Some(true),
font_italic: Some(true),
font_underline: Some(true),
font_overline: Some(true),
font_strikeout: Some(true),
font_weight: Some(700),
font_point_size: Some(24),
font_family: Some("Courier".into()),
letter_spacing: Some(5),
word_spacing: Some(10),
underline_style: Some(UnderlineStyle::DashUnderline),
vertical_alignment: Some(CharVerticalAlignment::SuperScript),
..Default::default()
};
c.set_char_format(&fmt).unwrap();
let read_c = doc.cursor_at(0);
let applied = read_c.char_format().unwrap();
assert_eq!(applied.font_bold, Some(true));
doc.undo().unwrap();
let read_c = doc.cursor_at(0);
let reverted = read_c.char_format().unwrap();
assert_ne!(reverted.font_bold, Some(true));
doc.redo().unwrap();
let read_c = doc.cursor_at(0);
let reapplied = read_c.char_format().unwrap();
assert_eq!(reapplied.font_bold, Some(true));
}
#[test]
fn undo_redo_merge_text_format() {
let doc = new_doc("Hello world");
let c = doc.cursor();
c.move_position(MoveOperation::NextCharacter, MoveMode::KeepAnchor, 5);
let fmt1 = TextFormat {
font_bold: Some(true),
..Default::default()
};
c.set_char_format(&fmt1).unwrap();
let fmt2 = TextFormat {
font_italic: Some(true),
font_underline: Some(true),
font_family: Some("Monospace".into()),
..Default::default()
};
c.merge_char_format(&fmt2).unwrap();
let read_c = doc.cursor_at(0);
let merged = read_c.char_format().unwrap();
assert_eq!(merged.font_bold, Some(true));
assert_eq!(merged.font_italic, Some(true));
doc.undo().unwrap();
let read_c = doc.cursor_at(0);
let after_undo = read_c.char_format().unwrap();
assert_eq!(after_undo.font_bold, Some(true));
assert_ne!(after_undo.font_italic, Some(true));
doc.redo().unwrap();
let read_c = doc.cursor_at(0);
let after_redo = read_c.char_format().unwrap();
assert_eq!(after_redo.font_bold, Some(true));
assert_eq!(after_redo.font_italic, Some(true));
}
#[test]
fn undo_redo_set_block_format() {
let doc = new_doc("Hello");
let c = doc.cursor();
let fmt = BlockFormat {
alignment: Some(Alignment::Center),
heading_level: Some(1),
indent: Some(2),
marker: Some(MarkerType::Checked),
..Default::default()
};
c.set_block_format(&fmt).unwrap();
let read_c = doc.cursor_at(0);
let applied = read_c.block_format().unwrap();
assert_eq!(applied.alignment, Some(Alignment::Center));
assert_eq!(applied.heading_level, Some(1));
doc.undo().unwrap();
let read_c = doc.cursor_at(0);
let reverted = read_c.block_format().unwrap();
assert_ne!(reverted.alignment, Some(Alignment::Center));
doc.redo().unwrap();
let read_c = doc.cursor_at(0);
let reapplied = read_c.block_format().unwrap();
assert_eq!(reapplied.alignment, Some(Alignment::Center));
}
#[test]
fn undo_redo_set_frame_format() {
let doc = new_doc("Hello");
let c = doc.cursor();
let frame_id = {
let stats = doc.stats();
assert!(stats.frame_count >= 1);
2
};
let fmt = text_document::FrameFormat {
width: Some(500),
height: Some(300),
top_margin: Some(10),
bottom_margin: Some(10),
left_margin: Some(20),
right_margin: Some(20),
padding: Some(5),
border: Some(1),
..Default::default()
};
c.set_frame_format(frame_id, &fmt).unwrap();
doc.undo().unwrap();
assert!(doc.can_redo());
doc.redo().unwrap();
assert!(doc.can_undo());
}
#[test]
fn undo_redo_replace_text() {
let doc = new_doc("foo bar foo baz");
let opts = FindOptions::default();
let count = doc
.replace_text(
"foo",
"XXX",
true,
&text_document::ReplaceOptions::new(opts.clone()),
)
.unwrap();
assert_eq!(count, 2);
assert_eq!(doc.to_plain_text().unwrap(), "XXX bar XXX baz");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "foo bar foo baz");
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "XXX bar XXX baz");
}
#[test]
fn multiple_undo_redo_sequence() {
let doc = new_doc("A");
let c = doc.cursor_at(1);
c.begin_edit_block();
c.insert_text("B").unwrap();
c.end_edit_block();
assert_eq!(doc.to_plain_text().unwrap(), "AB");
let c = doc.cursor_at(2);
c.begin_edit_block();
c.insert_text("C").unwrap();
c.end_edit_block();
assert_eq!(doc.to_plain_text().unwrap(), "ABC");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "AB");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "A");
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "AB");
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "ABC");
}
#[test]
fn undo_redo_delete_char_with_selection() {
let doc = new_doc("Hello world");
let c = doc.cursor();
c.move_position(MoveOperation::NextCharacter, MoveMode::KeepAnchor, 5);
c.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), " world");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), " world");
}
#[test]
fn undo_redo_delete_previous_char() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.delete_previous_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hell");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hell");
}
#[test]
fn undo_edit_block_groups_operations() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.begin_edit_block();
c.insert_text(" ").unwrap();
c.insert_text("world").unwrap();
c.end_edit_block();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
}
#[test]
fn no_undo_after_set_plain_text() {
let doc = new_doc("Hello");
assert!(!doc.can_undo());
}
#[test]
fn clear_undo_redo_stack() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_text(" world").unwrap();
assert!(doc.can_undo());
doc.clear_undo_redo();
assert!(!doc.can_undo());
assert!(!doc.can_redo());
}
#[test]
fn undo_marks_the_document_modified_again() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_text(" world").unwrap();
doc.set_modified(false);
assert!(!doc.is_modified());
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
assert!(
doc.is_modified(),
"an undo that reverted text must leave the document dirty, or the host \
skips its write-back and keeps the pre-undo version"
);
}
#[test]
fn redo_marks_the_document_modified_again() {
let doc = new_doc("Hello");
let c = doc.cursor_at(5);
c.insert_text(" world").unwrap();
doc.undo().unwrap();
doc.set_modified(false);
doc.redo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
assert!(
doc.is_modified(),
"a redo re-applies the edit; that is a change"
);
}
#[test]
fn undo_on_an_empty_history_leaves_a_clean_document_clean() {
let doc = new_doc("Hello");
doc.set_modified(false);
assert!(!doc.can_undo());
doc.undo().unwrap();
assert!(
!doc.is_modified(),
"a keystroke that did nothing must not mark a clean document dirty"
);
doc.redo().unwrap();
assert!(!doc.is_modified());
}
#[test]
fn break_undo_merge_splits_a_typing_burst_in_two() {
let doc = new_doc("");
let c = doc.cursor_at(0);
c.insert_text("before").unwrap();
doc.break_undo_merge();
c.insert_text(" after").unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "before after");
doc.undo().unwrap();
assert_eq!(
doc.to_plain_text().unwrap(),
"before",
"one undo must take back only the burst after the break"
);
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "");
}
#[test]
fn without_the_break_the_same_two_bursts_are_one_step() {
let doc = new_doc("");
let c = doc.cursor_at(0);
c.insert_text("before").unwrap();
c.insert_text(" after").unwrap();
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "");
}
#[test]
fn break_undo_merge_is_harmless_with_no_history() {
let doc = new_doc("Hello");
doc.break_undo_merge();
assert!(!doc.can_undo());
}
#[test]
fn the_undo_limit_is_unset_by_default() {
let doc = new_doc("Hello");
assert_eq!(doc.undo_limit(), None);
}
#[test]
fn setting_an_undo_limit_drops_the_oldest_entries() {
let doc = new_doc("");
doc.set_undo_limit(Some(2));
assert_eq!(doc.undo_limit(), Some(2));
for word in ["one", "two", "three"] {
let c = doc.cursor_at(doc.character_count());
c.insert_text(word).unwrap();
doc.break_undo_merge();
}
assert_eq!(doc.to_plain_text().unwrap(), "onetwothree");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "onetwo");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "one");
assert!(
!doc.can_undo(),
"the oldest entry was dropped to honour the limit, so \"one\" is the \
floor — the far end of a long history is the part nobody reaches for"
);
}
#[test]
fn clearing_the_undo_limit_restores_an_unbounded_history() {
let doc = new_doc("");
doc.set_undo_limit(Some(1));
doc.set_undo_limit(None);
assert_eq!(doc.undo_limit(), None);
for word in ["one", "two", "three"] {
let c = doc.cursor_at(doc.character_count());
c.insert_text(word).unwrap();
doc.break_undo_merge();
}
doc.undo().unwrap();
doc.undo().unwrap();
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "");
}