use super::*;
#[test]
fn new_buffer_is_empty() {
let buf = Buffer::new();
assert_eq!(buf.line_count(), 0);
assert!(buf.is_empty());
assert!(!buf.is_modified());
assert!(buf.file_path().is_none());
}
#[test]
fn with_id() {
let id = BufferId::from_raw(42);
let buf = Buffer::with_id(id);
assert_eq!(buf.id(), id);
assert_eq!(buf.line_count(), 0);
assert!(buf.is_empty());
assert!(!buf.is_modified());
}
#[test]
fn from_string_multi_line() {
let buf = Buffer::from_string("Hello\nWorld\nTest");
assert_eq!(buf.line_count(), 3);
assert_eq!(buf.line(0), Some("Hello"));
assert_eq!(buf.line(1), Some("World"));
assert_eq!(buf.line(2), Some("Test"));
}
#[test]
fn from_string_not_modified() {
let buf = Buffer::from_string("content");
assert!(!buf.is_modified());
}
#[test]
fn default_is_new() {
let buf = Buffer::default();
assert_eq!(buf.line_count(), 0);
assert!(buf.is_empty());
}
#[test]
fn modified_flag() {
let mut buf = Buffer::from_string("test");
assert!(!buf.is_modified());
buf.set_modified(true);
assert!(buf.is_modified());
buf.set_modified(false);
assert!(!buf.is_modified());
}
#[test]
fn file_path_none_by_default() {
let buf = Buffer::new();
assert!(buf.file_path().is_none());
}
#[test]
fn set_and_get_file_path() {
let mut buf = Buffer::new();
buf.set_file_path(Some("/tmp/test.txt".to_string()));
assert_eq!(buf.file_path(), Some("/tmp/test.txt"));
buf.set_file_path(None);
assert!(buf.file_path().is_none());
}
#[test]
fn line_out_of_bounds() {
let buf = Buffer::from_string("Hello");
assert!(buf.line(1).is_none());
assert!(buf.line(100).is_none());
}
#[test]
fn line_len_valid() {
let buf = Buffer::from_string("Hello\nWorld!");
assert_eq!(buf.line_len(0), Some(5));
assert_eq!(buf.line_len(1), Some(6));
}
#[test]
fn line_len_out_of_bounds() {
let buf = Buffer::from_string("Hello");
assert!(buf.line_len(1).is_none());
}
#[test]
fn line_len_unicode() {
let buf = Buffer::from_string("H\u{00e9}llo");
assert_eq!(buf.line_len(0), Some(5));
}
#[test]
fn content_empty() {
let buf = Buffer::new();
assert_eq!(buf.content(), "");
}
#[test]
fn content_single_line() {
let buf = Buffer::from_string("Hello");
assert_eq!(buf.content(), "Hello");
}
#[test]
fn set_content_replaces_all() {
let mut buf = Buffer::from_string("old");
buf.set_content("new\ncontent");
assert_eq!(buf.line_count(), 2);
assert_eq!(buf.line(0), Some("new"));
assert_eq!(buf.line(1), Some("content"));
assert!(buf.is_modified());
}
#[test]
fn set_content_empty_clears_lines() {
let mut buf = Buffer::from_string("Hello\nWorld");
buf.set_content("");
assert_eq!(buf.line_count(), 0);
assert!(buf.is_empty());
assert!(buf.is_modified());
}
#[test]
fn line_hash_valid_line() {
let buf = Buffer::from_string("Hello\nWorld");
let hash0 = buf.line_hash(0);
let hash1 = buf.line_hash(1);
assert!(hash0.is_some());
assert!(hash1.is_some());
assert_ne!(hash0, hash1);
}
#[test]
fn line_hash_out_of_bounds() {
let buf = Buffer::from_string("Hello");
assert!(buf.line_hash(1).is_none());
}
#[test]
fn line_hash_consistent() {
let buf = Buffer::from_string("Hello");
let hash1 = buf.line_hash(0);
let hash2 = buf.line_hash(0);
assert_eq!(hash1, hash2);
}
#[test]
fn line_hashes_returns_all() {
let buf = Buffer::from_string("A\nB\nC");
let hashes = buf.line_hashes();
assert_eq!(hashes.len(), 3);
}
#[test]
fn line_hashes_empty_buffer() {
let buf = Buffer::new();
let hashes = buf.line_hashes();
assert!(hashes.is_empty());
}
#[test]
fn insert_at_empty_text_noop() {
let mut buf = Buffer::from_string("Hello");
buf.insert_at(Position::new(0, 0), "");
assert_eq!(buf.line(0), Some("Hello"));
assert!(!buf.is_modified());
}
#[test]
fn insert_at_empty_buffer() {
let mut buf = Buffer::new();
buf.insert_at(Position::origin(), "Hello");
assert_eq!(buf.line_count(), 1);
assert_eq!(buf.line(0), Some("Hello"));
assert!(buf.is_modified());
}
#[test]
fn insert_at_end() {
let mut buf = Buffer::from_string("Hello");
buf.insert_at(Position::new(0, 5), " World");
assert_eq!(buf.line(0), Some("Hello World"));
}
#[test]
fn insert_at_clamped_position() {
let mut buf = Buffer::from_string("Hello");
buf.insert_at(Position::new(0, 100), "!");
assert_eq!(buf.line(0), Some("Hello!"));
}
#[test]
fn insert_at_clamped_line() {
let mut buf = Buffer::from_string("Hello");
buf.insert_at(Position::new(100, 0), "!");
assert_eq!(buf.line(0), Some("!Hello"));
}
#[test]
fn insert_marks_modified() {
let mut buf = Buffer::from_string("test");
buf.insert_at(Position::new(0, 0), "x");
assert!(buf.is_modified());
}
#[test]
fn insert_unicode() {
let mut buf = Buffer::from_string("H\u{00e9}llo");
buf.insert_at(Position::new(0, 5), " World");
assert_eq!(buf.line(0), Some("H\u{00e9}llo World"));
}
#[test]
fn delete_at_zero_count() {
let mut buf = Buffer::from_string("Hello");
let deleted = buf.delete_at(Position::new(0, 0), 0);
assert_eq!(deleted, "");
assert_eq!(buf.line(0), Some("Hello"));
assert!(!buf.is_modified());
}
#[test]
fn delete_at_empty_buffer() {
let mut buf = Buffer::new();
let deleted = buf.delete_at(Position::origin(), 5);
assert_eq!(deleted, "");
}
#[test]
fn delete_at_single_char() {
let mut buf = Buffer::from_string("Hello");
let deleted = buf.delete_at(Position::new(0, 0), 1);
assert_eq!(deleted, "H");
assert_eq!(buf.line(0), Some("ello"));
}
#[test]
fn delete_at_multiple_chars() {
let mut buf = Buffer::from_string("Hello World");
let deleted = buf.delete_at(Position::new(0, 0), 6);
assert_eq!(deleted, "Hello ");
assert_eq!(buf.line(0), Some("World"));
}
#[test]
fn delete_at_newline_merges_lines() {
let mut buf = Buffer::from_string("Hello\nWorld");
let deleted = buf.delete_at(Position::new(0, 5), 1);
assert_eq!(deleted, "\n");
assert_eq!(buf.line_count(), 1);
assert_eq!(buf.line(0), Some("HelloWorld"));
}
#[test]
fn delete_at_across_lines() {
let mut buf = Buffer::from_string("Hello\nWorld");
let deleted = buf.delete_at(Position::new(0, 3), 5);
assert_eq!(deleted, "lo\nWo");
assert_eq!(buf.line_count(), 1);
assert_eq!(buf.line(0), Some("Helrld"));
}
#[test]
fn delete_at_end_of_line_no_next() {
let mut buf = Buffer::from_string("Hello");
let deleted = buf.delete_at(Position::new(0, 5), 1);
assert_eq!(deleted, "");
}
#[test]
fn delete_at_marks_modified() {
let mut buf = Buffer::from_string("test");
buf.delete_at(Position::new(0, 0), 1);
assert!(buf.is_modified());
}
#[test]
fn delete_at_nothing_deleted_not_modified() {
let mut buf = Buffer::from_string("test");
let deleted = buf.delete_at(Position::new(0, 4), 0);
assert_eq!(deleted, "");
assert!(!buf.is_modified());
}
#[test]
fn position_to_byte_start() {
let buf = Buffer::from_string("Hello\nWorld");
assert_eq!(buf.position_to_byte(Position::new(0, 0)), 0);
}
#[test]
fn position_to_byte_mid_line() {
let buf = Buffer::from_string("Hello\nWorld");
assert_eq!(buf.position_to_byte(Position::new(0, 5)), 5);
}
#[test]
fn position_to_byte_second_line() {
let buf = Buffer::from_string("Hello\nWorld");
assert_eq!(buf.position_to_byte(Position::new(1, 0)), 6);
assert_eq!(buf.position_to_byte(Position::new(1, 5)), 11);
}
#[test]
fn position_to_byte_empty_buffer() {
let buf = Buffer::new();
assert_eq!(buf.position_to_byte(Position::new(0, 0)), 0);
}
#[test]
fn position_to_byte_unicode() {
let buf = Buffer::from_string("H\u{00e9}llo");
assert_eq!(buf.position_to_byte(Position::new(0, 0)), 0); assert_eq!(buf.position_to_byte(Position::new(0, 1)), 1); assert_eq!(buf.position_to_byte(Position::new(0, 2)), 3); }
#[test]
fn byte_to_position_start() {
let buf = Buffer::from_string("Hello\nWorld");
assert_eq!(buf.byte_to_position(0), Position::new(0, 0));
}
#[test]
fn byte_to_position_mid_line() {
let buf = Buffer::from_string("Hello\nWorld");
assert_eq!(buf.byte_to_position(3), Position::new(0, 3));
}
#[test]
fn byte_to_position_at_newline() {
let buf = Buffer::from_string("Hello\nWorld");
assert_eq!(buf.byte_to_position(5), Position::new(0, 5));
}
#[test]
fn byte_to_position_second_line() {
let buf = Buffer::from_string("Hello\nWorld");
assert_eq!(buf.byte_to_position(6), Position::new(1, 0));
assert_eq!(buf.byte_to_position(11), Position::new(1, 5));
}
#[test]
fn byte_to_position_past_end() {
let buf = Buffer::from_string("Hello\nWorld");
let result = buf.byte_to_position(100);
assert_eq!(result, Position::new(1, 5));
}
#[test]
fn byte_to_position_empty_buffer() {
let buf = Buffer::new();
assert_eq!(buf.byte_to_position(0), Position::new(0, 0));
}
#[test]
fn byte_position_roundtrip() {
let buf = Buffer::from_string("Hello\nWorld\nTest");
for line in 0..buf.line_count() {
for col in 0..=buf.line_len(line).unwrap() {
let pos = Position::new(line, col);
let byte = buf.position_to_byte(pos);
let back = buf.byte_to_position(byte);
assert_eq!(pos, back, "Roundtrip failed for {pos:?} (byte={byte})");
}
}
}
#[test]
fn byte_position_roundtrip_unicode() {
let buf = Buffer::from_string("H\u{00e9}llo\nWorld");
for line in 0..buf.line_count() {
for col in 0..=buf.line_len(line).unwrap() {
let pos = Position::new(line, col);
let byte = buf.position_to_byte(pos);
let back = buf.byte_to_position(byte);
assert_eq!(pos, back, "Unicode roundtrip failed for {pos:?}");
}
}
}
#[test]
fn buffer_clone() {
let mut buf = Buffer::from_string("Hello");
buf.set_file_path(Some("/test".to_string()));
buf.set_modified(true);
let cloned = buf.clone();
assert_eq!(cloned.id(), buf.id());
assert_eq!(cloned.content(), buf.content());
assert_eq!(cloned.file_path(), buf.file_path());
assert_eq!(cloned.is_modified(), buf.is_modified());
}
#[test]
fn insert_at_clamped_empty_buffer_with_text() {
let mut buf = Buffer::new();
buf.insert_at(Position::new(10, 10), "text");
assert_eq!(buf.line(0), Some("text"));
}
#[test]
fn delete_at_clamped() {
let mut buf = Buffer::from_string("Hello");
let deleted = buf.delete_at(Position::new(0, 100), 1);
assert_eq!(deleted, "");
}
#[test]
fn insert_then_delete() {
let mut buf = Buffer::from_string("Hello");
buf.insert_at(Position::new(0, 5), " World");
assert_eq!(buf.content(), "Hello World");
buf.delete_at(Position::new(0, 5), 6);
assert_eq!(buf.content(), "Hello");
}
#[test]
fn multiple_newline_inserts() {
let mut buf = Buffer::new();
buf.insert_at(Position::origin(), "Line1\nLine2\nLine3");
assert_eq!(buf.line_count(), 3);
assert_eq!(buf.line(0), Some("Line1"));
assert_eq!(buf.line(1), Some("Line2"));
assert_eq!(buf.line(2), Some("Line3"));
}
#[test]
fn substitute_delete_insert_preserves_lines() {
let mut buf = Buffer::from_string("aaa\nbbb\naaa");
assert_eq!(buf.line_count(), 3);
let deleted = buf.delete_range(Position::new(2, 0), Position::new(2, 3));
eprintln!("After delete line 2: content={:?} lines={}", buf.content(), buf.line_count());
for i in 0..buf.line_count() {
eprintln!(" line {}: {:?}", i, buf.line(i));
}
assert_eq!(deleted, "aaa");
buf.insert_at(Position::new(2, 0), "zzz");
eprintln!("After insert line 2: content={:?} lines={}", buf.content(), buf.line_count());
for i in 0..buf.line_count() {
eprintln!(" line {}: {:?}", i, buf.line(i));
}
assert_eq!(buf.line(0), Some("aaa"), "line 0 after step 1");
assert_eq!(buf.line(1), Some("bbb"), "line 1 after step 1");
assert_eq!(buf.line(2), Some("zzz"), "line 2 after step 1");
let deleted = buf.delete_range(Position::new(0, 0), Position::new(0, 3));
eprintln!("After delete line 0: content={:?} lines={}", buf.content(), buf.line_count());
for i in 0..buf.line_count() {
eprintln!(" line {}: {:?}", i, buf.line(i));
}
assert_eq!(deleted, "aaa");
buf.insert_at(Position::new(0, 0), "zzz");
eprintln!("After insert line 0: content={:?} lines={}", buf.content(), buf.line_count());
for i in 0..buf.line_count() {
eprintln!(" line {}: {:?}", i, buf.line(i));
}
assert_eq!(buf.line(0), Some("zzz"), "line 0 final");
assert_eq!(buf.line(1), Some("bbb"), "line 1 final");
assert_eq!(buf.line(2), Some("zzz"), "line 2 final");
assert_eq!(buf.content(), "zzz\nbbb\nzzz");
}
#[test]
fn delete_range_on_large_buffer_exercises_chunk_iteration() {
let line = "abcdefghijklmnopqrstuvwxyz"; let content: String = (0..50).map(|_| line).collect::<Vec<_>>().join("\n");
let mut buf = Buffer::from_string(&content);
let deleted = buf.delete_range(Position::new(25, 5), Position::new(25, 10));
assert_eq!(deleted, "fghij", "should extract the correct byte range across chunks");
}
#[test]
fn extract_byte_range_skips_early_chunks_and_breaks_after() {
use std::fmt::Write;
let mut text = String::new();
for i in 0..200 {
writeln!(text, "line number {i:05} with padding").unwrap();
}
let mut buf = Buffer::from_string(&text);
assert!(buf.content().len() > 2048, "buffer should have multiple chunks");
let deleted = buf.delete_range(Position::new(199, 0), Position::new(199, 4));
assert_eq!(deleted, "line", "should extract text from the last chunk");
}
#[test]
fn delete_at_at_very_end_yields_empty_not_modified() {
let mut buf = Buffer::from_string("abc");
let deleted = buf.delete_at(Position::new(0, 3), 0);
assert_eq!(deleted, "");
assert!(!buf.is_modified(), "empty deletion should not set modified");
}
#[test]
fn delete_range_zero_width_not_modified() {
let mut buf = Buffer::from_string("hello\nworld");
let deleted = buf.delete_range(Position::new(0, 3), Position::new(0, 3));
assert_eq!(deleted, "");
assert!(!buf.is_modified(), "zero-width delete_range should not set modified");
}
#[test]
fn delete_range_reversed_positions() {
let mut buf = Buffer::from_string("hello\nworld");
let deleted = buf.delete_range(Position::new(0, 5), Position::new(0, 2));
assert_eq!(deleted, "llo", "reversed range should still delete correctly");
assert!(buf.is_modified());
}
#[test]
fn extract_byte_range_spanning_multiple_chunks() {
use std::fmt::Write;
let mut text = String::new();
for i in 0..300 {
writeln!(text, "data line {i:05} padding chars here").unwrap();
}
let mut buf = Buffer::from_string(&text);
let deleted = buf.delete_range(Position::new(100, 0), Position::new(200, 0));
assert!(!deleted.is_empty(), "should extract cross-chunk content");
assert!(deleted.starts_with("data line 00100"));
}
#[test]
fn delete_range_on_empty_buffer() {
let mut buf = Buffer::new();
let deleted = buf.delete_range(Position::new(0, 0), Position::new(1, 5));
assert!(deleted.is_empty());
}
#[test]
fn from_string_single_newline() {
let buf = Buffer::from_string("\n");
assert_eq!(buf.line_count(), 0, "single newline normalizes to empty");
}