use text_document::{MoveMode, MoveOperation, SelectionType, TextDocument, TextFormat};
fn new_doc(text: &str) -> TextDocument {
let doc = TextDocument::new();
doc.set_plain_text(text).unwrap();
doc
}
#[test]
fn delete_char_at_end_of_document_is_noop() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(5);
cursor.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
assert_eq!(doc.character_count(), 5);
}
#[test]
fn delete_char_at_end_of_multiblock_document_is_noop() {
let doc = new_doc("Hello\nWorld");
let cursor = doc.cursor_at(11);
cursor.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello\nWorld");
assert_eq!(doc.character_count(), 10);
assert_eq!(doc.block_count(), 2);
}
#[test]
fn delete_char_at_end_preserves_character_count_consistency() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(5);
for _ in 0..3 {
cursor.delete_char().unwrap();
}
assert_eq!(doc.character_count(), 5);
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
}
#[test]
fn delete_char_at_end_of_block_merges_with_next() {
let doc = new_doc("Hello\nWorld");
let cursor = doc.cursor_at(5);
cursor.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "HelloWorld");
assert_eq!(doc.block_count(), 1);
assert_eq!(doc.character_count(), 10);
}
#[test]
fn delete_previous_char_at_start_of_block_merges_with_previous() {
let doc = new_doc("Hello\nWorld");
let cursor = doc.cursor_at(6);
cursor.delete_previous_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "HelloWorld");
assert_eq!(doc.block_count(), 1);
assert_eq!(doc.character_count(), 10);
}
#[test]
fn delete_char_merges_three_blocks() {
let doc = new_doc("A\nB\nC");
let cursor = doc.cursor_at(1);
cursor.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "AB\nC");
assert_eq!(doc.block_count(), 2);
let cursor2 = doc.cursor_at(2);
cursor2.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "ABC");
assert_eq!(doc.block_count(), 1);
}
#[test]
fn move_next_block_from_last_block() {
let doc = new_doc("First\nSecond\nThird");
let c = doc.cursor_at(13); let moved = c.move_position(MoveOperation::NextBlock, MoveMode::MoveAnchor, 1);
assert!(moved);
assert!(c.at_end());
}
#[test]
fn move_next_block_from_end_of_last_block() {
let doc = new_doc("First\nSecond\nThird");
let c = doc.cursor_at(18); let moved = c.move_position(MoveOperation::NextBlock, MoveMode::MoveAnchor, 1);
assert!(!moved);
assert_eq!(c.position(), 18);
}
#[test]
fn move_up_from_first_block() {
let doc = new_doc("First\nSecond");
let c = doc.cursor_at(2); let moved = c.move_position(MoveOperation::Up, MoveMode::MoveAnchor, 1);
assert!(moved); assert_eq!(c.position(), 0);
let moved2 = c.move_position(MoveOperation::Up, MoveMode::MoveAnchor, 1);
assert!(!moved2);
assert_eq!(c.position(), 0);
}
#[test]
fn move_down_from_last_block() {
let doc = new_doc("First\nSecond");
let c = doc.cursor_at(8); let moved = c.move_position(MoveOperation::Down, MoveMode::MoveAnchor, 1);
assert!(moved);
assert!(c.at_end());
}
#[test]
fn move_next_character_crosses_block_boundary() {
let doc = new_doc("AB\nCD");
let c = doc.cursor_at(2); c.move_position(MoveOperation::NextCharacter, MoveMode::MoveAnchor, 1);
assert_eq!(c.position(), 3);
c.move_position(MoveOperation::NextCharacter, MoveMode::MoveAnchor, 1);
assert_eq!(c.position(), 4);
}
#[test]
fn move_previous_character_crosses_block_boundary() {
let doc = new_doc("AB\nCD");
let c = doc.cursor_at(3); c.move_position(MoveOperation::PreviousCharacter, MoveMode::MoveAnchor, 1);
assert_eq!(c.position(), 2);
c.move_position(MoveOperation::PreviousCharacter, MoveMode::MoveAnchor, 1);
assert_eq!(c.position(), 1);
assert_eq!(c.block_number(), 0);
}
#[test]
fn move_operations_on_empty_document() {
let doc = TextDocument::new();
let c = doc.cursor();
assert!(!c.move_position(MoveOperation::NextCharacter, MoveMode::MoveAnchor, 1));
assert_eq!(c.position(), 0);
assert!(!c.move_position(MoveOperation::PreviousCharacter, MoveMode::MoveAnchor, 1));
assert_eq!(c.position(), 0);
assert!(!c.move_position(MoveOperation::NextBlock, MoveMode::MoveAnchor, 1));
assert_eq!(c.position(), 0);
assert!(!c.move_position(MoveOperation::NextWord, MoveMode::MoveAnchor, 1));
assert_eq!(c.position(), 0);
}
#[test]
fn insert_empty_string_is_noop() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(3);
cursor.insert_text("").unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
assert_eq!(cursor.position(), 3);
}
#[test]
fn insert_text_with_newline_inserts_literal() {
let doc = new_doc("AC");
let cursor = doc.cursor_at(1);
cursor.insert_text("BB").unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "ABBC");
assert_eq!(doc.block_count(), 1);
}
#[test]
fn insert_block_at_document_start() {
let doc = new_doc("Hello");
let cursor = doc.cursor();
cursor.insert_block().unwrap();
assert_eq!(doc.block_count(), 2);
let text = doc.to_plain_text().unwrap();
assert_eq!(text, "\nHello");
}
#[test]
fn insert_block_with_selection_deletes_then_splits() {
let doc = new_doc("Hello World");
let cursor = doc.cursor();
cursor.set_position(0, MoveMode::MoveAnchor);
cursor.set_position(5, MoveMode::KeepAnchor);
cursor.insert_block().unwrap();
assert_eq!(doc.block_count(), 2);
let text = doc.to_plain_text().unwrap();
assert_eq!(text, "\n World");
}
#[test]
fn insert_block_with_selection_undo_restores_original() {
let doc = new_doc("Hello World");
let cursor = doc.cursor();
cursor.set_position(0, MoveMode::MoveAnchor);
cursor.set_position(5, MoveMode::KeepAnchor);
cursor.insert_block().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "\n World");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello World");
assert_eq!(doc.block_count(), 1);
}
#[test]
fn insert_block_at_document_end() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(5);
cursor.insert_block().unwrap();
assert_eq!(doc.block_count(), 2);
assert_eq!(doc.to_plain_text().unwrap(), "Hello\n");
}
#[test]
fn backward_selection_selected_text() {
let doc = new_doc("Hello world");
let cursor = doc.cursor_at(5);
cursor.set_position(0, MoveMode::KeepAnchor);
assert!(cursor.has_selection());
assert_eq!(cursor.anchor(), 5);
assert_eq!(cursor.position(), 0);
assert_eq!(cursor.selection_start(), 0);
assert_eq!(cursor.selection_end(), 5);
assert_eq!(cursor.selected_text().unwrap(), "Hello");
}
#[test]
fn backward_selection_remove_selected_text() {
let doc = new_doc("Hello world");
let cursor = doc.cursor_at(5);
cursor.set_position(0, MoveMode::KeepAnchor);
let removed = cursor.remove_selected_text().unwrap();
assert_eq!(removed, "Hello");
assert_eq!(doc.to_plain_text().unwrap(), " world");
}
#[test]
fn backward_selection_delete_char() {
let doc = new_doc("Hello world");
let cursor = doc.cursor_at(5);
cursor.set_position(0, MoveMode::KeepAnchor);
cursor.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), " world");
}
#[test]
fn backward_selection_insert_text_replaces() {
let doc = new_doc("Hello world");
let cursor = doc.cursor_at(5);
cursor.set_position(0, MoveMode::KeepAnchor);
cursor.insert_text("Goodbye").unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Goodbye world");
}
#[test]
fn navigate_through_empty_block() {
let doc = new_doc("Hello\n\nWorld");
assert_eq!(doc.block_count(), 3);
let c = doc.cursor();
c.move_position(MoveOperation::NextBlock, MoveMode::MoveAnchor, 1);
assert_eq!(c.block_number(), 1);
assert_eq!(c.position_in_block(), 0);
assert!(c.at_block_start());
assert!(c.at_block_end());
c.move_position(MoveOperation::NextBlock, MoveMode::MoveAnchor, 1);
assert_eq!(c.block_number(), 2);
assert_eq!(c.position_in_block(), 0);
}
#[test]
fn delete_char_on_empty_block_merges() {
let doc = new_doc("Hello\n\nWorld");
assert_eq!(doc.block_count(), 3);
let cursor = doc.cursor_at(6);
cursor.delete_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello\nWorld");
assert_eq!(doc.block_count(), 2);
}
#[test]
fn select_word_on_whitespace() {
let doc = new_doc("Hello World");
let c = doc.cursor_at(6); c.select(SelectionType::WordUnderCursor);
let _ = c.selected_text().unwrap();
}
#[test]
fn select_word_at_punctuation() {
let doc = new_doc("Hello, World");
let c = doc.cursor_at(5); c.select(SelectionType::WordUnderCursor);
let _ = c.selected_text().unwrap();
}
#[test]
fn multi_cursor_selection_adjusted_by_edit() {
let doc = new_doc("Hello World");
let c1 = doc.cursor_at(0);
let c2 = doc.cursor_at(6);
c2.set_position(11, MoveMode::KeepAnchor);
assert!(c2.has_selection());
c1.insert_text("XX").unwrap();
assert_eq!(c2.anchor(), 8); assert_eq!(c2.position(), 13); }
#[test]
fn multi_cursor_cross_block_insert_adjusts() {
let doc = new_doc("Hello World");
let c1 = doc.cursor_at(5);
let c2 = doc.cursor_at(11);
c1.insert_block().unwrap();
assert!(c2.position() > 11);
assert_eq!(doc.block_count(), 2);
assert_eq!(doc.to_plain_text().unwrap(), "Hello\n World");
}
#[test]
fn insert_image_with_selection_replaces_text() {
let doc = new_doc("Hello World");
let cursor = doc.cursor();
cursor.set_position(6, MoveMode::MoveAnchor);
cursor.set_position(11, MoveMode::KeepAnchor);
cursor.insert_image("photo.png", 100, 50).unwrap();
assert_eq!(doc.character_count(), 7);
}
#[test]
fn insert_image_with_cross_block_selection_replaces() {
let doc = new_doc("Hello\nWorld");
let cursor = doc.cursor();
cursor.set_position(3, MoveMode::MoveAnchor);
cursor.set_position(9, MoveMode::KeepAnchor);
cursor.insert_image("bridge.png", 200, 100).unwrap();
assert_eq!(doc.character_count(), 6);
assert_eq!(doc.block_count(), 1);
}
#[test]
fn insert_image_with_selection_undo_restores() {
let doc = new_doc("Hello World");
let cursor = doc.cursor();
cursor.set_position(6, MoveMode::MoveAnchor);
cursor.set_position(11, MoveMode::KeepAnchor);
cursor.insert_image("photo.png", 100, 50).unwrap();
assert_eq!(doc.character_count(), 7);
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello World");
}
#[test]
fn insert_formatted_text_cross_block_selection_replaces() {
let doc = new_doc("Hello\nWorld");
let cursor = doc.cursor();
cursor.set_position(3, MoveMode::MoveAnchor);
cursor.set_position(9, MoveMode::KeepAnchor);
let fmt = TextFormat {
font_bold: Some(true),
..Default::default()
};
cursor.insert_formatted_text("XY", &fmt).unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "HelXYld");
assert_eq!(doc.block_count(), 1);
}
#[test]
fn insert_formatted_text_cross_block_selection_undo() {
let doc = new_doc("Hello\nWorld");
let cursor = doc.cursor();
cursor.set_position(3, MoveMode::MoveAnchor);
cursor.set_position(9, MoveMode::KeepAnchor);
let fmt = TextFormat {
font_bold: Some(true),
..Default::default()
};
cursor.insert_formatted_text("XY", &fmt).unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "HelXYld");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello\nWorld");
assert_eq!(doc.block_count(), 2);
}