use std::sync::{Arc, Mutex};
use text_document::{DocumentEvent, ListStyle, MoveMode, MoveOperation, TextDocument};
fn new_doc(text: &str) -> TextDocument {
let doc = TextDocument::new();
doc.set_plain_text(text).unwrap();
doc.poll_events(); doc
}
#[test]
fn remove_selected_text_emits_contents_changed() {
let doc = new_doc("Hello world");
let cursor = doc.cursor();
cursor.move_position(MoveOperation::NextCharacter, MoveMode::KeepAnchor, 5);
let deleted = cursor.remove_selected_text().unwrap();
assert_eq!(deleted, "Hello");
let events = doc.poll_events();
assert!(
events
.iter()
.any(|e| matches!(e, DocumentEvent::ContentsChanged { .. })),
"expected ContentsChanged from remove_selected_text, got: {:?}",
events
);
}
#[test]
fn remove_selected_text_fires_callback() {
let doc = new_doc("Hello world");
let received = Arc::new(Mutex::new(Vec::new()));
let r = received.clone();
let _sub = doc.on_change(move |e| r.lock().unwrap().push(e));
let cursor = doc.cursor();
cursor.move_position(MoveOperation::NextCharacter, MoveMode::KeepAnchor, 5);
cursor.remove_selected_text().unwrap();
let events = received.lock().unwrap();
assert!(
events
.iter()
.any(|e| matches!(e, DocumentEvent::ContentsChanged { .. })),
"callback should have received ContentsChanged from remove_selected_text, got: {:?}",
*events
);
}
#[test]
fn insert_frame_emits_contents_changed() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(5);
cursor.insert_frame().unwrap();
let events = doc.poll_events();
assert!(
events
.iter()
.any(|e| matches!(e, DocumentEvent::ContentsChanged { .. })),
"expected ContentsChanged from insert_frame, got: {:?}",
events
);
}
#[test]
fn create_list_emits_contents_changed() {
let doc = new_doc("Hello");
let cursor = doc.cursor();
cursor.create_list(ListStyle::Disc).unwrap();
let events = doc.poll_events();
assert!(
events
.iter()
.any(|e| matches!(e, DocumentEvent::ContentsChanged { .. })),
"expected ContentsChanged from create_list, got: {:?}",
events
);
}
#[test]
fn insert_list_emits_contents_changed() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(5);
cursor.insert_list(ListStyle::Decimal).unwrap();
let events = doc.poll_events();
assert!(
events
.iter()
.any(|e| matches!(e, DocumentEvent::ContentsChanged { .. })),
"expected ContentsChanged from insert_list, got: {:?}",
events
);
}
#[test]
fn poll_and_callback_both_receive_events() {
let doc = new_doc("Hello");
let received = Arc::new(Mutex::new(Vec::new()));
let r = received.clone();
let _sub = doc.on_change(move |e| r.lock().unwrap().push(e));
let cursor = doc.cursor_at(5);
cursor.insert_text(" world").unwrap();
let cb_events = received.lock().unwrap();
assert!(
cb_events
.iter()
.any(|e| matches!(e, DocumentEvent::ContentsChanged { .. })),
"callback should have received ContentsChanged"
);
drop(cb_events);
let poll_events = doc.poll_events();
assert!(
poll_events
.iter()
.any(|e| matches!(e, DocumentEvent::ContentsChanged { .. })),
"poll_events should also receive ContentsChanged"
);
}
#[test]
fn poll_events_drains_independently_from_callbacks() {
let doc = new_doc("Hello");
let _sub = doc.on_change(|_| {});
let cursor = doc.cursor_at(5);
cursor.insert_text(" world").unwrap();
let events1 = doc.poll_events();
assert!(!events1.is_empty());
let events2 = doc.poll_events();
assert!(events2.is_empty());
cursor.insert_text("!").unwrap();
let events3 = doc.poll_events();
assert!(!events3.is_empty());
}
#[test]
fn plain_text_cache_invalidated_on_edit() {
let doc = new_doc("Hello");
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
let cursor = doc.cursor_at(5);
cursor.insert_text(" world").unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
}
#[test]
fn plain_text_cache_invalidated_on_undo() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(5);
cursor.insert_text(" world").unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello world");
doc.undo().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
}
#[test]
fn plain_text_cache_invalidated_on_clear() {
let doc = new_doc("Hello");
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
doc.clear().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "");
}
#[test]
fn wait_timeout_returns_result_for_fast_operation() {
let doc = new_doc("Hello world");
let op = doc.set_markdown("# Heading\n\nParagraph").unwrap();
let result = op.wait_timeout(std::time::Duration::from_secs(5));
assert!(result.is_some(), "operation should complete within timeout");
let import_result = result.unwrap().unwrap();
assert!(import_result.block_count >= 2);
}
#[test]
fn join_previous_edit_block_groups_with_begin() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(5);
cursor.join_previous_edit_block();
cursor.insert_text(" ").unwrap();
cursor.insert_text("world").unwrap();
cursor.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 remove_selected_text_no_selection_returns_empty() {
let doc = new_doc("Hello");
let cursor = doc.cursor_at(3);
let result = cursor.remove_selected_text().unwrap();
assert_eq!(result, "");
}
#[test]
fn delete_previous_char_at_start_is_noop() {
let doc = new_doc("Hello");
let cursor = doc.cursor();
cursor.delete_previous_char().unwrap();
assert_eq!(doc.to_plain_text().unwrap(), "Hello");
}