use super::*;
#[test]
fn empty_rope() {
let r = Rope::new();
assert!(r.is_empty());
assert_eq!(r.byte_len(), 0);
assert_eq!(r.char_len(), 0);
assert_eq!(r.line_count(), 0);
assert_eq!(r.line(0), None);
assert_eq!(r.content(), "");
}
#[test]
fn from_empty_str() {
let r = Rope::from_str("");
assert!(r.is_empty());
assert_eq!(r.line_count(), 0);
}
#[test]
fn from_single_line() {
let r = Rope::from_str("hello");
assert!(!r.is_empty());
assert_eq!(r.byte_len(), 5);
assert_eq!(r.char_len(), 5);
assert_eq!(r.line_count(), 1);
assert_eq!(r.line(0), Some("hello"));
assert_eq!(r.line(1), None);
assert_eq!(r.content(), "hello");
}
#[test]
fn from_two_lines() {
let r = Rope::from_str("hello\nworld");
assert_eq!(r.line_count(), 2);
assert_eq!(r.line(0), Some("hello"));
assert_eq!(r.line(1), Some("world"));
assert_eq!(r.content(), "hello\nworld");
}
#[test]
fn from_trailing_newline() {
let r = Rope::from_str("abc\n");
assert_eq!(r.line_count(), 2);
assert_eq!(r.line(0), Some("abc"));
assert_eq!(r.line(1), Some(""));
}
#[test]
fn from_multiple_trailing_newlines() {
let r = Rope::from_str("abc\n\n");
assert_eq!(r.line_count(), 3);
assert_eq!(r.line(0), Some("abc"));
assert_eq!(r.line(1), Some(""));
assert_eq!(r.line(2), Some(""));
}
#[test]
fn from_only_newlines() {
let r = Rope::from_str("\n\n\n");
assert_eq!(r.line_count(), 4);
assert_eq!(r.line(0), Some(""));
assert_eq!(r.line(1), Some(""));
assert_eq!(r.line(2), Some(""));
assert_eq!(r.line(3), Some(""));
}
#[test]
fn roundtrip_from_str_content() {
let texts = [
"hello",
"hello\nworld",
"hello\nworld\n",
"\n",
"\n\n",
"abc\ndef\nghi\njkl",
"",
];
for text in texts {
let r = Rope::from_str(text);
assert_eq!(r.content(), text, "roundtrip failed for {text:?}");
}
}
#[test]
fn large_rope_roundtrip() {
let line = "the quick brown fox jumps over the lazy dog\n";
let text: String = line.repeat(100);
let r = Rope::from_str(&text);
assert_eq!(r.content(), text);
assert_eq!(r.line_count(), 101);
for i in 0..100 {
assert_eq!(
r.line(i),
Some("the quick brown fox jumps over the lazy dog"),
"line {i} mismatch"
);
}
}
#[test]
fn large_rope_line_count() {
use std::fmt::Write;
let mut text = String::new();
for i in 0..1000 {
writeln!(text, "line {i}").unwrap();
}
let r = Rope::from_str(&text);
assert_eq!(r.line_count(), 1001);
}
#[test]
fn unicode_multibyte() {
let r = Rope::from_str("héllo\nwörld");
assert_eq!(r.line_count(), 2);
assert_eq!(r.char_len(), 11);
assert_eq!(r.line(0), Some("héllo"));
assert_eq!(r.line(1), Some("wörld"));
assert_eq!(r.line_len(0), Some(5)); }
#[test]
fn unicode_cjk() {
let r = Rope::from_str("你好\n世界");
assert_eq!(r.line_count(), 2);
assert_eq!(r.char_len(), 5); assert_eq!(r.line(0), Some("你好"));
assert_eq!(r.line(1), Some("世界"));
}
#[test]
fn unicode_emoji() {
let r = Rope::from_str("🎉🎊\n🥳");
assert_eq!(r.line_count(), 2);
assert_eq!(r.line(0), Some("🎉🎊"));
assert_eq!(r.line(1), Some("🥳"));
}
#[test]
fn position_to_byte_simple() {
let r = Rope::from_str("hello\nworld");
assert_eq!(r.position_to_byte(0, 0), 0);
assert_eq!(r.position_to_byte(0, 5), 5);
assert_eq!(r.position_to_byte(1, 0), 6);
assert_eq!(r.position_to_byte(1, 5), 11);
}
#[test]
fn position_to_byte_unicode() {
let r = Rope::from_str("héllo\nworld");
assert_eq!(r.position_to_byte(0, 0), 0);
assert_eq!(r.position_to_byte(0, 1), 1); assert_eq!(r.position_to_byte(0, 2), 3); assert_eq!(r.position_to_byte(1, 0), 7); }
#[test]
fn byte_to_position_simple() {
let r = Rope::from_str("hello\nworld");
assert_eq!(r.byte_to_position(0), (0, 0));
assert_eq!(r.byte_to_position(5), (0, 5));
assert_eq!(r.byte_to_position(6), (1, 0));
assert_eq!(r.byte_to_position(11), (1, 5));
}
#[test]
fn byte_to_position_unicode() {
let r = Rope::from_str("héllo\nworld");
assert_eq!(r.byte_to_position(0), (0, 0));
assert_eq!(r.byte_to_position(1), (0, 1)); assert_eq!(r.byte_to_position(3), (0, 2)); assert_eq!(r.byte_to_position(7), (1, 0)); }
#[test]
fn position_roundtrip() {
let r = Rope::from_str("abc\ndef\nghi");
for line in 0..3 {
for col in 0..3 {
let byte = r.position_to_byte(line, col);
let (l, c) = r.byte_to_position(byte);
assert_eq!((l, c), (line, col), "roundtrip failed for ({line}, {col})");
}
}
}
#[test]
fn char_byte_roundtrip() {
let r = Rope::from_str("héllo wörld");
for ci in 0..=r.char_len() {
let byte = r.char_to_byte(ci);
let back = r.byte_to_char(byte);
assert_eq!(back, ci, "char/byte roundtrip failed for char {ci}");
}
}
#[test]
fn clone_is_structural_sharing() {
let r1 = Rope::from_str("hello\nworld");
let r2 = r1.clone();
assert_eq!(r1, r2);
assert!(Arc::ptr_eq(&r1.root, &r2.root));
}
#[test]
fn clone_independence() {
let r1 = Rope::from_str("hello\nworld");
let r2 = r1.insert(5, " there");
assert_eq!(r1.content(), "hello\nworld");
assert_eq!(r2.content(), "hello there\nworld");
}
#[test]
fn insert_into_empty() {
let r = Rope::new();
let r2 = r.insert(0, "hello");
assert_eq!(r2.content(), "hello");
}
#[test]
fn insert_at_beginning() {
let r = Rope::from_str("world");
let r2 = r.insert(0, "hello ");
assert_eq!(r2.content(), "hello world");
}
#[test]
fn insert_at_end() {
let r = Rope::from_str("hello");
let r2 = r.insert(5, " world");
assert_eq!(r2.content(), "hello world");
}
#[test]
fn insert_in_middle() {
let r = Rope::from_str("hllo");
let r2 = r.insert(1, "e");
assert_eq!(r2.content(), "hello");
}
#[test]
fn insert_newline() {
let r = Rope::from_str("helloworld");
let r2 = r.insert(5, "\n");
assert_eq!(r2.line_count(), 2);
assert_eq!(r2.line(0), Some("hello"));
assert_eq!(r2.line(1), Some("world"));
}
#[test]
fn insert_multiline() {
let r = Rope::from_str("ac");
let r2 = r.insert(1, "b\nd\ne");
assert_eq!(r2.content(), "ab\nd\nec");
assert_eq!(r2.line_count(), 3);
}
#[test]
fn insert_empty_text_is_noop() {
let r = Rope::from_str("hello");
let r2 = r.insert(3, "");
assert!(Arc::ptr_eq(&r.root, &r2.root));
}
#[test]
fn insert_preserves_original() {
let original = Rope::from_str("hello");
let modified = original.insert(5, " world");
assert_eq!(original.content(), "hello");
assert_eq!(modified.content(), "hello world");
}
#[test]
fn insert_unicode() {
let r = Rope::from_str("hllo");
let r2 = r.insert(1, "é");
assert_eq!(r2.content(), "héllo");
assert_eq!(r2.char_len(), 5);
assert_eq!(r2.byte_len(), 6);
}
#[test]
fn remove_from_empty() {
let r = Rope::new();
let r2 = r.remove(0..5);
assert!(r2.is_empty());
}
#[test]
fn remove_entire_content() {
let r = Rope::from_str("hello");
let r2 = r.remove(0..5);
assert!(r2.is_empty());
assert_eq!(r2.content(), "");
}
#[test]
fn remove_beginning() {
let r = Rope::from_str("hello world");
let r2 = r.remove(0..6);
assert_eq!(r2.content(), "world");
}
#[test]
fn remove_end() {
let r = Rope::from_str("hello world");
let r2 = r.remove(5..11);
assert_eq!(r2.content(), "hello");
}
#[test]
fn remove_middle() {
let r = Rope::from_str("hello world");
let r2 = r.remove(5..6); assert_eq!(r2.content(), "helloworld");
}
#[test]
fn remove_newline() {
let r = Rope::from_str("hello\nworld");
let r2 = r.remove(5..6); assert_eq!(r2.content(), "helloworld");
assert_eq!(r2.line_count(), 1);
}
#[test]
fn remove_empty_range() {
let r = Rope::from_str("hello");
let r2 = r.remove(2..2);
assert!(Arc::ptr_eq(&r.root, &r2.root));
}
#[test]
fn remove_preserves_original() {
let original = Rope::from_str("hello world");
let modified = original.remove(5..11);
assert_eq!(original.content(), "hello world");
assert_eq!(modified.content(), "hello");
}
#[test]
fn remove_across_lines() {
let r = Rope::from_str("abc\ndef\nghi");
let r2 = r.remove(2..9);
assert_eq!(r2.content(), "abhi");
assert_eq!(r2.line_count(), 1);
}
#[test]
fn insert_remove_roundtrip() {
let r = Rope::from_str("hello world");
let r2 = r.insert(5, " there");
assert_eq!(r2.content(), "hello there world");
let r3 = r2.remove(5..11); assert_eq!(r3.content(), "hello world");
}
#[test]
fn lines_iterator() {
let r = Rope::from_str("abc\ndef\nghi");
assert!(r.lines().eq(["abc", "def", "ghi"]));
}
#[test]
fn lines_iterator_empty() {
let r = Rope::new();
assert!(r.lines().next().is_none());
}
#[test]
fn lines_exact_size() {
let r = Rope::from_str("a\nb\nc");
let lines = r.lines();
assert_eq!(lines.len(), 3);
}
#[test]
fn chunks_iterator() {
let r = Rope::from_str("hello");
let chunks: Vec<&str> = r.chunks().collect();
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], "hello");
}
#[test]
fn chunks_concatenate_to_content() {
let line = "a long line for testing chunk splitting purposes\n";
let text: String = line.repeat(100);
let r = Rope::from_str(&text);
let from_chunks: String = r.chunks().collect();
assert_eq!(from_chunks, text);
}
#[test]
fn equality_same_content() {
let r1 = Rope::from_str("hello\nworld");
let r2 = Rope::from_str("hello\nworld");
assert_eq!(r1, r2);
}
#[test]
fn equality_different_content() {
let r1 = Rope::from_str("hello");
let r2 = Rope::from_str("world");
assert_ne!(r1, r2);
}
#[test]
fn equality_different_structure_same_content() {
let r1 = Rope::from_str("abcdef");
let r2 = {
let r = Rope::from_str("abc");
r.insert(3, "def")
};
assert_eq!(r1, r2);
}
#[test]
fn display_shows_content() {
let r = Rope::from_str("hello\nworld");
assert_eq!(format!("{r}"), "hello\nworld");
}
#[test]
fn line_len_ascii() {
let r = Rope::from_str("hello\nworld");
assert_eq!(r.line_len(0), Some(5));
assert_eq!(r.line_len(1), Some(5));
assert_eq!(r.line_len(2), None);
}
#[test]
fn line_len_unicode() {
let r = Rope::from_str("héllo\nwörld");
assert_eq!(r.line_len(0), Some(5)); assert_eq!(r.line_len(1), Some(5));
}
#[test]
fn metrics_empty() {
let m = Metrics::from_text("");
assert_eq!(m, Metrics::default());
}
#[test]
fn metrics_simple() {
let m = Metrics::from_text("hello");
assert_eq!(m.byte_len, 5);
assert_eq!(m.char_len, 5);
assert_eq!(m.line_count, 1);
}
#[test]
fn metrics_multiline() {
let m = Metrics::from_text("abc\ndef");
assert_eq!(m.byte_len, 7);
assert_eq!(m.char_len, 7);
assert_eq!(m.line_count, 2);
}
#[test]
fn metrics_trailing_newline() {
let m = Metrics::from_text("abc\n");
assert_eq!(m.line_count, 1);
}
#[test]
fn metrics_unicode() {
let m = Metrics::from_text("héllo");
assert_eq!(m.byte_len, 6); assert_eq!(m.char_len, 5);
assert_eq!(m.line_count, 1);
}
#[test]
fn metrics_sum() {
let m1 = Metrics::from_text("abc\n");
let m2 = Metrics::from_text("def");
let sum = Metrics::sum([m1, m2].into_iter());
assert_eq!(sum.byte_len, 7);
assert_eq!(sum.char_len, 7);
assert_eq!(sum.line_count, 2); }
#[test]
fn chunk_text_small() {
let chunks = chunk_text("hello");
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], "hello");
}
#[test]
fn chunk_text_newline_aligned() {
let line = "a line of text here!\n";
let text: String = line.repeat(100);
let chunks = chunk_text(&text);
for (i, chunk) in chunks.iter().enumerate() {
if i < chunks.len() - 1 {
assert!(
chunk.ends_with('\n'),
"chunk {i} doesn't end with newline: {:?}",
&chunk[chunk.len().saturating_sub(20)..]
);
}
assert!(chunk.len() <= MAX_CHUNK_BYTES, "chunk {i} too large: {} bytes", chunk.len());
}
let reconstructed: String = chunks.into_iter().collect();
assert_eq!(reconstructed, text);
}
#[test]
fn chunk_text_oversized_line() {
let long_line: String = "x".repeat(MAX_CHUNK_BYTES + 500);
let chunks = chunk_text(&long_line);
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], long_line);
}
#[test]
fn build_tree_single_leaf() {
let leaves = vec![RopeNode::new_leaf("hello".to_string())];
let root = build_tree(leaves);
assert!(root.is_leaf());
}
#[test]
fn build_tree_multiple_leaves() {
let leaves: Vec<Arc<RopeNode>> = (0..20)
.map(|i| RopeNode::new_leaf(format!("leaf {i}\n")))
.collect();
let root = build_tree(leaves);
assert!(!root.is_leaf());
assert_eq!(root.metrics.line_count, 20);
}
fn check_alignment(node: &RopeNode, is_rightmost: bool) -> bool {
match &node.kind {
NodeKind::Leaf { text } => {
if text.is_empty() {
return true;
}
is_rightmost || text.ends_with('\n')
}
NodeKind::Internal { children } => {
for (i, child) in children.iter().enumerate() {
let child_rightmost = is_rightmost && i == children.len() - 1;
if !check_alignment(child, child_rightmost) {
return false;
}
}
true
}
}
}
#[test]
fn alignment_after_construction() {
use std::fmt::Write;
let mut text = String::new();
for i in 0..50 {
writeln!(text, "line {i}").unwrap();
}
let r = Rope::from_str(&text);
assert!(check_alignment(&r.root, true), "alignment broken after construction");
}
#[test]
fn alignment_after_insert() {
let r = Rope::from_str("abc\ndef\nghi");
let r2 = r.insert(4, "XYZ\n");
assert!(check_alignment(&r2.root, true), "alignment broken after insert");
assert_eq!(r2.content(), "abc\nXYZ\ndef\nghi");
}
#[test]
fn alignment_after_remove_newline() {
let r = Rope::from_str("abc\ndef\nghi");
let r2 = r.remove(3..4);
assert_eq!(r2.content(), "abcdef\nghi");
assert!(check_alignment(&r2.root, true), "alignment broken after removing newline");
}
#[test]
fn stress_sequential_inserts() {
let mut r = Rope::new();
for i in 0..100 {
let text = format!("line {i}\n");
r = r.insert(r.byte_len(), &text);
}
assert_eq!(r.line_count(), 101);
for i in 0..100 {
assert_eq!(r.line(i), Some(format!("line {i}").as_str()));
}
}
#[test]
fn stress_sequential_removes() {
use std::fmt::Write;
let mut text = String::new();
for i in 0..100 {
writeln!(text, "line {i}").unwrap();
}
let mut r = Rope::from_str(&text);
for _ in 0..100 {
if r.is_empty() {
break;
}
let first_line_bytes = r.line(0).map_or(0, |l| l.len() + 1); r = r.remove(0..first_line_bytes);
}
assert!(r.is_empty());
}
#[test]
fn stress_insert_remove_mixed() {
let mut r = Rope::from_str("initial");
for i in 0..50 {
r = r.insert(0, &format!("prefix{i}\n"));
r = r.insert(r.byte_len(), &format!("\nsuffix{i}"));
}
let content = r.content();
assert!(content.starts_with("prefix49\n"));
assert!(content.ends_with("\nsuffix49"));
}
#[test]
fn single_char() {
let r = Rope::from_str("x");
assert_eq!(r.line_count(), 1);
assert_eq!(r.line(0), Some("x"));
assert_eq!(r.byte_len(), 1);
assert_eq!(r.char_len(), 1);
}
#[test]
fn single_newline() {
let r = Rope::from_str("\n");
assert_eq!(r.line_count(), 2);
assert_eq!(r.line(0), Some(""));
assert_eq!(r.line(1), Some(""));
}
#[test]
fn empty_lines() {
let r = Rope::from_str("\n\n\n");
assert_eq!(r.line_count(), 4);
for i in 0..4 {
assert_eq!(r.line(i), Some(""));
}
}
#[test]
fn insert_past_end_clamps() {
let r = Rope::from_str("hello");
let r2 = r.insert(100, " world");
assert_eq!(r2.content(), "hello world");
}
#[test]
fn remove_past_end_clamps() {
let r = Rope::from_str("hello");
let r2 = r.remove(3..100);
assert_eq!(r2.content(), "hel");
}
#[test]
fn default_is_empty() {
let r = Rope::default();
assert!(r.is_empty());
}
#[test]
fn position_on_empty() {
let r = Rope::new();
assert_eq!(r.position_to_byte(0, 0), 0);
assert_eq!(r.byte_to_position(0), (0, 0));
assert_eq!(r.char_to_byte(0), 0);
assert_eq!(r.byte_to_char(0), 0);
}
#[test]
fn lines_match_split_newline() {
let texts = [
"hello\nworld",
"abc",
"abc\ndef\nghi\n",
"\n\n\n",
"single",
"a\nb\nc\nd\ne\nf",
];
for text in texts {
let r = Rope::from_str(text);
let expected: Vec<&str> = text.split('\n').collect();
for (i, &exp) in expected.iter().enumerate() {
assert_eq!(r.line(i), Some(exp), "line {i} mismatch for text {text:?}");
}
assert_eq!(r.line_count(), expected.len(), "line_count mismatch for text {text:?}");
}
let r = Rope::from_str("");
assert_eq!(r.line_count(), 0);
}
fn make_big_rope() -> (Rope, String) {
use std::fmt::Write;
let mut text = String::new();
for i in 0..2000 {
writeln!(text, "line {i:04}").unwrap();
}
let r = Rope::from_str(&text);
(r, text)
}
fn make_huge_rope() -> (Rope, String) {
use std::fmt::Write;
let mut text = String::new();
for i in 0..20_000 {
writeln!(text, "line number {i:05} data here").unwrap();
}
let r = Rope::from_str(&text);
(r, text)
}
#[test]
fn debug_short_content() {
let r = Rope::from_str("hello\nworld");
let dbg = format!("{r:?}");
assert!(dbg.contains("Rope"), "should contain struct name");
assert!(dbg.contains("byte_len"), "should contain byte_len field");
assert!(dbg.contains("line_count"), "should contain line_count field");
assert!(dbg.contains("hello\\nworld"), "should contain content preview");
}
#[test]
fn debug_long_content_truncated() {
let long_line = "a".repeat(120);
let r = Rope::from_str(&long_line);
let dbg = format!("{r:?}");
assert!(dbg.contains("..."), "long content should be truncated with ...");
assert!(dbg.contains("byte_len"), "should contain byte_len field");
}
#[test]
fn eq_different_byte_len_returns_false() {
let r1 = Rope::from_str("a");
let r2 = Rope::from_str("ab");
assert_ne!(r1, r2);
}
#[test]
fn eq_same_byte_len_different_content() {
let r1 = Rope::from_str("abc");
let r2 = Rope::from_str("xyz");
assert_ne!(r1, r2);
}
#[test]
fn eq_identical_multi_chunk_ropes() {
let (r1, text) = make_big_rope();
let r2 = Rope::from_str(&text);
assert_eq!(r1, r2);
}
#[test]
fn eq_multi_chunk_different_content() {
use std::fmt::Write;
let mut text1 = String::new();
let mut text2 = String::new();
for i in 0..2000 {
writeln!(text1, "line {i:04}").unwrap();
writeln!(text2, "LINE {i:04}").unwrap();
}
let r1 = Rope::from_str(&text1);
let r2 = Rope::from_str(&text2);
assert_ne!(r1, r2);
}
#[test]
fn remove_range_fully_past_end() {
let r = Rope::from_str("hello");
let r2 = r.remove(5..100);
assert_eq!(r2.content(), "hello");
assert!(Arc::ptr_eq(&r.root, &r2.root));
}
#[test]
fn remove_range_start_past_end() {
let r = Rope::from_str("hello");
let r2 = r.remove(50..100);
assert_eq!(r2.content(), "hello");
assert!(Arc::ptr_eq(&r.root, &r2.root));
}
#[test]
fn chunk_text_oversized_line_with_newline_after() {
let long_line = "x".repeat(MAX_CHUNK_BYTES + 500);
let text = format!("{long_line}\nshort\n");
let chunks = chunk_text(&text);
let reconstructed: String = chunks.iter().map(String::as_str).collect();
assert_eq!(reconstructed, text);
assert!(chunks.len() >= 2, "should have at least 2 chunks");
assert!(chunks[0].ends_with('\n'), "first chunk should end with newline");
}
#[test]
fn chunk_text_multibyte_at_boundary() {
let two_byte_char = "é";
let count = MAX_CHUNK_BYTES / 2 + 10; let long_segment: String = two_byte_char.repeat(count);
let text = format!("{long_segment}\nshort\n");
let chunks = chunk_text(&text);
let reconstructed: String = chunks.iter().map(String::as_str).collect();
assert_eq!(reconstructed, text, "multibyte roundtrip must preserve text");
for (i, chunk) in chunks.iter().enumerate() {
assert!(!chunk.is_empty(), "chunk {i} should not be empty");
}
}
#[test]
fn chunk_text_no_newline_oversized() {
let text = "a".repeat(MAX_CHUNK_BYTES * 3);
let chunks = chunk_text(&text);
assert_eq!(chunks.len(), 1, "no-newline text should be one chunk");
assert_eq!(chunks[0], text);
}
#[test]
fn nodes_to_root_zero_nodes() {
let r = Rope::from_str("hello");
let r2 = r.remove(0..5);
assert!(r2.is_empty());
assert_eq!(r2.content(), "");
}
#[test]
fn nodes_to_root_single_node() {
let r = Rope::from_str("ab");
let r2 = r.insert(1, "X");
assert_eq!(r2.content(), "aXb");
}
#[test]
fn nodes_to_root_few_nodes() {
let r = Rope::from_str("hello");
let insert_text = "x\n".repeat(500); let r2 = r.insert(3, &insert_text);
let expected = format!("hel{insert_text}lo");
assert_eq!(r2.content(), expected);
}
#[test]
fn nodes_to_root_many_nodes() {
let r = Rope::from_str("hello");
let insert_text = "line\n".repeat(5000); let r2 = r.insert(3, &insert_text);
let expected = format!("hel{insert_text}lo");
assert_eq!(r2.content(), expected);
}
#[test]
fn line_at_internal_nodes() {
let (r, text) = make_big_rope();
let expected: Vec<&str> = text.split('\n').collect();
for &idx in &[0, 1, 100, 500, 999, 1500, 1999] {
assert_eq!(r.line(idx), Some(expected[idx]), "line {idx} mismatch on big rope");
}
assert_eq!(r.line(2000), Some(""));
assert_eq!(r.line(2001), None);
}
#[test]
fn pos_to_byte_internal_nodes() {
let (r, text) = make_big_rope();
let lines: Vec<&str> = text.split('\n').collect();
for &line_idx in &[0, 50, 500, 1000, 1999] {
let byte_offset = r.position_to_byte(line_idx, 0);
let expected: usize = lines[..line_idx].iter().map(|l| l.len() + 1).sum();
assert_eq!(byte_offset, expected, "pos_to_byte start of line {line_idx}");
}
let byte_at_col3 = r.position_to_byte(100, 3);
let line_start: usize = lines[..100].iter().map(|l| l.len() + 1).sum();
assert_eq!(byte_at_col3, line_start + 3, "pos_to_byte line 100 col 3");
}
#[test]
fn byte_to_pos_internal_nodes() {
let (r, text) = make_big_rope();
let lines: Vec<&str> = text.split('\n').collect();
let mut byte_offset = 0usize;
for (line_idx, line) in lines.iter().enumerate() {
if line_idx > 1999 {
break;
}
if line_idx % 200 == 0 {
let (l, c) = r.byte_to_position(byte_offset);
assert_eq!((l, c), (line_idx, 0), "byte_to_pos at start of line {line_idx}");
if !line.is_empty() {
let mid = byte_offset + line.len() / 2;
let (ml, mc) = r.byte_to_position(mid);
assert_eq!(ml, line_idx, "byte_to_pos mid line {line_idx} wrong line");
assert_eq!(mc, line.len() / 2, "byte_to_pos mid line {line_idx} wrong col");
}
}
byte_offset += line.len() + 1; }
let (l, c) = r.byte_to_position(text.len());
assert_eq!((l, c), (2000, 0), "byte_to_pos at end of text");
}
#[test]
fn char_to_byte_internal_nodes() {
let (r, text) = make_big_rope();
for &offset in &[0, 100, 500, 5000, 10000] {
assert_eq!(r.char_to_byte(offset), offset, "char_to_byte({offset}) on ASCII big rope");
}
assert_eq!(r.char_to_byte(text.len()), text.len());
}
#[test]
fn char_to_byte_internal_nodes_unicode() {
use std::fmt::Write;
let mut text = String::new();
for i in 0..2000 {
writeln!(text, "línea {i:04}").unwrap(); }
let r = Rope::from_str(&text);
for &ci in &[0, 10, 100, 1000, 5000] {
let byte = r.char_to_byte(ci);
let back = r.byte_to_char(byte);
assert_eq!(back, ci, "char/byte roundtrip failed for char {ci} on big unicode rope");
}
}
#[test]
fn byte_to_char_internal_nodes() {
let (r, text) = make_big_rope();
for &offset in &[0, 100, 500, 5000, 10000] {
assert_eq!(r.byte_to_char(offset), offset, "byte_to_char({offset}) on ASCII big rope");
}
assert_eq!(r.byte_to_char(text.len()), text.len());
}
#[test]
fn insert_into_big_rope_beginning() {
let (r, text) = make_big_rope();
let r2 = r.insert(0, "PREFIX\n");
assert_eq!(r2.content(), format!("PREFIX\n{text}"));
assert!(check_alignment(&r2.root, true), "alignment broken after insert at beginning");
}
#[test]
fn insert_into_big_rope_middle() {
let (r, text) = make_big_rope();
let mid = text.len() / 2;
let insert_pos = text[..mid].rfind('\n').unwrap() + 1;
let inserted = "INSERTED LINE\n";
let r2 = r.insert(insert_pos, inserted);
let expected = format!("{}{inserted}{}", &text[..insert_pos], &text[insert_pos..]);
assert_eq!(r2.content(), expected);
assert!(check_alignment(&r2.root, true), "alignment broken after insert in middle");
}
#[test]
fn insert_into_big_rope_end() {
let (r, text) = make_big_rope();
let r2 = r.insert(text.len(), "SUFFIX");
assert_eq!(r2.content(), format!("{text}SUFFIX"));
}
#[test]
fn insert_large_text_into_big_rope() {
use std::fmt::Write;
let (r, text) = make_big_rope();
let mut large_insert = String::new();
for i in 0..500 {
writeln!(large_insert, "inserted {i:04}").unwrap();
}
let insert_pos = 100;
let r2 = r.insert(insert_pos, &large_insert);
let expected = format!("{}{large_insert}{}", &text[..insert_pos], &text[insert_pos..]);
assert_eq!(r2.content(), expected);
assert!(check_alignment(&r2.root, true), "alignment broken after large insert");
}
#[test]
fn remove_from_big_rope_beginning() {
let (r, text) = make_big_rope();
let r2 = r.remove(0..500);
assert_eq!(r2.content(), &text[500..]);
assert!(check_alignment(&r2.root, true), "alignment broken after remove from beginning");
}
#[test]
fn remove_from_big_rope_middle() {
let (r, text) = make_big_rope();
let start = 3000;
let end = 6000;
let r2 = r.remove(start..end);
let expected = format!("{}{}", &text[..start], &text[end..]);
assert_eq!(r2.content(), expected);
assert!(check_alignment(&r2.root, true), "alignment broken after remove from middle");
}
#[test]
fn remove_from_big_rope_end() {
let (r, text) = make_big_rope();
let start = text.len() - 500;
let r2 = r.remove(start..text.len());
assert_eq!(r2.content(), &text[..start]);
assert!(check_alignment(&r2.root, true), "alignment broken after remove from end");
}
#[test]
fn remove_entire_big_rope() {
let (r, text) = make_big_rope();
let r2 = r.remove(0..text.len());
assert!(r2.is_empty());
assert_eq!(r2.content(), "");
}
#[test]
fn remove_across_many_children() {
let (r, text) = make_big_rope();
let start = 1000;
let end = text.len() - 1000;
let r2 = r.remove(start..end);
let expected = format!("{}{}", &text[..start], &text[end..]);
assert_eq!(r2.content(), expected);
assert!(
check_alignment(&r2.root, true),
"alignment broken after removing across many children"
);
}
#[test]
fn remove_single_child_entirely() {
let (r, _text) = make_big_rope();
let r2 = r.remove(1024..2048);
assert!(check_alignment(&r2.root, true));
assert_eq!(r2.byte_len(), r.byte_len() - 1024);
}
#[test]
fn fixup_alignment_after_newline_removal() {
let (r, text) = make_big_rope();
let mid = text.len() / 2;
let nl_pos = text[..mid].rfind('\n').unwrap();
let r2 = r.remove(nl_pos..nl_pos + 1);
let expected = format!("{}{}", &text[..nl_pos], &text[nl_pos + 1..]);
assert_eq!(r2.content(), expected);
assert!(
check_alignment(&r2.root, true),
"alignment broken after removing newline in big rope"
);
}
#[test]
fn fixup_alignment_multiple_newline_removals() {
let (mut r, text) = make_big_rope();
let mut expected = text;
for _ in 0..10 {
if let Some(nl_pos) = expected[..expected.len() / 2].rfind('\n') {
r = r.remove(nl_pos..nl_pos + 1);
expected = format!("{}{}", &expected[..nl_pos], &expected[nl_pos + 1..]);
assert_eq!(r.content(), expected, "content mismatch after newline removal");
assert!(
check_alignment(&r.root, true),
"alignment broken during serial newline removals"
);
}
}
}
#[test]
fn huge_rope_line_access() {
let (r, text) = make_huge_rope();
let lines: Vec<&str> = text.split('\n').collect();
for &idx in &[0, 1, 1000, 5000, 10000, 15000, 19999] {
assert_eq!(r.line(idx), Some(lines[idx]), "line {idx} mismatch on huge rope");
}
}
#[test]
fn huge_rope_position_roundtrip() {
let (r, text) = make_huge_rope();
let lines: Vec<&str> = text.split('\n').collect();
let mut byte_offset = 0usize;
for (line_idx, line) in lines.iter().enumerate() {
if line_idx >= 20000 {
break;
}
if line_idx % 2000 == 0 {
let byte = r.position_to_byte(line_idx, 0);
assert_eq!(byte, byte_offset, "pos_to_byte line {line_idx} on huge rope");
let (l, c) = r.byte_to_position(byte);
assert_eq!((l, c), (line_idx, 0), "byte_to_pos line {line_idx} on huge rope");
}
byte_offset += line.len() + 1;
}
}
#[test]
fn huge_rope_char_byte_roundtrip() {
let (r, text) = make_huge_rope();
for &ci in &[0, 1000, 50_000, 200_000, text.len()] {
let byte = r.char_to_byte(ci);
let back = r.byte_to_char(byte);
assert_eq!(back, ci, "char/byte roundtrip failed at {ci} on huge rope");
}
}
#[test]
fn huge_rope_insert_and_remove() {
let (r, text) = make_huge_rope();
let mid = text.len() / 2;
let insert_pos = text[..mid].rfind('\n').unwrap() + 1;
let r2 = r.insert(insert_pos, "HUGE INSERT\n");
let expected = format!("{}{}{}", &text[..insert_pos], "HUGE INSERT\n", &text[insert_pos..]);
assert_eq!(r2.content(), expected);
let inserted_len = "HUGE INSERT\n".len();
let r3 = r2.remove(insert_pos..insert_pos + inserted_len);
assert_eq!(r3.content(), text, "insert/remove roundtrip on huge rope");
}
#[test]
fn chunks_skip_empty_leaves() {
let (r, text) = make_big_rope();
let r2 = r.remove(0..500);
let from_chunks: String = r2.chunks().collect();
assert_eq!(from_chunks, &text[500..]);
for (i, chunk) in r2.chunks().enumerate() {
assert!(!chunk.is_empty(), "chunk {i} should not be empty");
}
}
#[test]
fn insert_at_every_line_boundary_of_big_rope() {
let (r, text) = make_big_rope();
let lines: Vec<&str> = text.split('\n').collect();
let mut offset = 0usize;
for (i, line) in lines.iter().enumerate().take(100) {
let r2 = r.insert(offset, "X");
let expected_start = &text[..offset];
let expected_end = &text[offset..];
assert_eq!(
r2.content(),
format!("{expected_start}X{expected_end}"),
"insert at line {i} boundary failed"
);
offset += line.len() + 1;
}
}
#[test]
fn repeated_inserts_force_splits() {
let (mut r, _) = make_big_rope();
for i in 0..200 {
let text = format!("ins{i:04}\n");
r = r.insert(0, &text);
}
let content = r.content();
assert!(content.starts_with("ins0199\n"));
assert!(check_alignment(&r.root, true));
}
#[test]
fn insert_remove_roundtrip_big_rope() {
let (r, text) = make_big_rope();
let insert_text = "INSERTED CONTENT\n";
let pos = 5000;
let r2 = r.insert(pos, insert_text);
let r3 = r2.remove(pos..pos + insert_text.len());
assert_eq!(r3.content(), text, "insert/remove roundtrip on big rope");
}
#[test]
fn remove_spanning_internal_boundaries() {
let (r, text) = make_huge_rope();
let start = 10_000;
let end = 100_000;
let r2 = r.remove(start..end);
let expected = format!("{}{}", &text[..start], &text[end..]);
assert_eq!(r2.content(), expected);
assert!(
check_alignment(&r2.root, true),
"alignment broken after removing across internal boundaries"
);
}
#[test]
fn chunks_iter_skips_empty_leaf_node() {
let empty_leaf = RopeNode::new_leaf(String::new());
let real_leaf = RopeNode::new_leaf("hello".to_string());
let root = RopeNode::new_internal(vec![empty_leaf, real_leaf]);
let rope = Rope { root };
let chunks: Vec<&str> = rope.chunks().collect();
assert_eq!(chunks, vec!["hello"], "empty leaf should be skipped");
}
#[test]
fn chunks_iter_multiple_empty_leaves() {
let leaves = vec![
RopeNode::new_leaf(String::new()),
RopeNode::new_leaf("aaa\n".to_string()),
RopeNode::new_leaf(String::new()),
RopeNode::new_leaf("bbb".to_string()),
RopeNode::new_leaf(String::new()),
];
let root = RopeNode::new_internal(leaves);
let rope = Rope { root };
let chunks: Vec<&str> = rope.chunks().collect();
assert_eq!(chunks, vec!["aaa\n", "bbb"]);
}
#[test]
fn eq_multi_chunk_different_boundaries_same_content() {
use std::fmt::Write;
let mut text = String::new();
for i in 0..200 {
writeln!(text, "line {i:04}").unwrap();
}
let r1 = Rope::from_str(&text);
let mut r2 = Rope::new();
for i in 0..200 {
let line = format!("line {i:04}\n");
r2 = r2.insert(r2.byte_len(), &line);
}
assert_eq!(r1.content(), r2.content(), "content should match");
assert_eq!(r1, r2, "PartialEq should succeed despite different tree structure");
}
#[test]
fn eq_left_exhausted_right_has_more() {
let left = Rope {
root: RopeNode::new_internal(vec![RopeNode::new_leaf("abc".to_string())]),
};
let right = Rope {
root: RopeNode::new_internal(vec![
RopeNode::new_leaf("ab".to_string()),
RopeNode::new_leaf("c".to_string()),
]),
};
assert_eq!(left, right);
}
#[test]
fn eq_right_exhausted_left_has_remaining() {
let left = Rope {
root: RopeNode::new_internal(vec![
RopeNode::new_leaf("ab".to_string()),
RopeNode::new_leaf("c".to_string()),
]),
};
let right = Rope {
root: RopeNode::new_internal(vec![RopeNode::new_leaf("abc".to_string())]),
};
assert_eq!(left, right);
}
#[test]
fn eq_left_exhausted_right_not_empty() {
let left = Rope {
root: RopeNode::new_leaf("abc".to_string()),
};
let right = Rope {
root: RopeNode::new_internal(vec![
RopeNode::new_leaf("ab".to_string()),
RopeNode::new_leaf("d".to_string()),
]),
};
assert_ne!(left, right);
}
#[test]
fn floor_char_boundary_at_multibyte_boundary() {
let emoji = "\u{1F600}"; assert_eq!(emoji.len(), 4);
let s = format!("{}{emoji}rest", "a".repeat(1021));
let result = floor_char_boundary(&s, 1024);
assert_eq!(result, 1021, "should backtrack to start of 4-byte char");
let s2 = format!("{}{}tail", "b".repeat(1023), "\u{00E9}"); let result2 = floor_char_boundary(&s2, 1024);
assert_eq!(result2, 1023, "should backtrack to start of 2-byte char");
}
#[test]
fn floor_char_boundary_past_end() {
let s = "hello";
let result = floor_char_boundary(s, 100);
assert_eq!(result, 5, "should return s.len() when byte_idx >= len");
}
#[test]
fn floor_char_boundary_at_ascii() {
let s = "hello\u{00E9}world";
let result = floor_char_boundary(s, 3);
assert_eq!(result, 3, "ASCII boundary needs no backtracking");
}
#[test]
fn build_tree_exactly_bmax_leaves() {
let leaves: Vec<Arc<RopeNode>> = (0..B_MAX)
.map(|i| RopeNode::new_leaf(format!("leaf{i}\n")))
.collect();
let root = build_tree(leaves);
assert!(!root.is_leaf(), "B_MAX leaves should produce internal node");
if let NodeKind::Internal { children } = &root.kind {
assert_eq!(children.len(), B_MAX);
}
}
#[test]
fn build_tree_bmax_plus_one_leaves() {
let leaves: Vec<Arc<RopeNode>> = (0..=B_MAX)
.map(|i| RopeNode::new_leaf(format!("leaf{i}\n")))
.collect();
let root = build_tree(leaves);
assert!(!root.is_leaf());
assert_eq!(root.metrics.line_count, B_MAX + 1);
}
#[test]
fn nodes_to_root_empty_vec() {
let root = nodes_to_root(vec![]);
assert!(root.is_leaf());
assert_eq!(root.metrics.byte_len, 0);
}
#[test]
fn line_at_past_last_line_in_internal_node() {
let (r, _) = make_big_rope();
assert_eq!(r.line(2001), None);
}
#[test]
fn line_at_leaf_past_lines_returns_none() {
let node = RopeNode::new_leaf("hello".to_string());
assert_eq!(line_at(&node, 1), None);
}
#[test]
fn line_at_leaf_no_trailing_newline_last_segment() {
let node = RopeNode::new_leaf("abc\ndef".to_string());
assert_eq!(line_at(&node, 0), Some("abc"));
assert_eq!(line_at(&node, 1), Some("def")); assert_eq!(line_at(&node, 2), None); }
#[test]
fn pos_to_byte_past_last_line_in_internal() {
let (r, text) = make_big_rope();
let result = r.position_to_byte(99999, 0);
assert_eq!(result, text.len(), "past-end line should return total byte_len");
}
#[test]
fn pos_to_byte_leaf_past_lines_returns_text_len() {
let node = RopeNode::new_leaf("hello".to_string());
let result = pos_to_byte(&node, 5, 0);
assert_eq!(result, 5);
}
#[test]
fn remove_range_empty_on_internal_node() {
let (r, _text) = make_big_rope();
let cloned = remove_range(&r.root, 100..100);
assert_eq!(cloned.len(), 1);
assert_eq!(cloned[0].metrics.byte_len, r.byte_len());
assert!(!cloned[0].is_leaf());
let leaf_node = RopeNode::new_leaf("hello".to_string());
let cloned_leaf = remove_range(&leaf_node, 2..2);
assert_eq!(cloned_leaf.len(), 1);
assert!(cloned_leaf[0].is_leaf());
if let NodeKind::Leaf { text } = &cloned_leaf[0].kind {
assert_eq!(text, "hello");
}
}
#[test]
fn remove_range_leaf_full_removal() {
let leaf = RopeNode::new_leaf("x".to_string());
let result = remove_range(&leaf, 0..1);
assert!(result.is_empty(), "removing all content from leaf should return empty vec");
}
#[test]
fn find_child_for_byte_past_end() {
let children = vec![
RopeNode::new_leaf("aaa\n".to_string()),
RopeNode::new_leaf("bbb\n".to_string()),
];
let (idx, local) = find_child_for_byte(&children, 100);
assert_eq!(idx, 1, "should return last child index");
assert_eq!(local, 4, "should return last child's byte_len");
}
#[test]
fn find_child_for_byte_exact_boundary() {
let children = vec![
RopeNode::new_leaf("aaa\n".to_string()), RopeNode::new_leaf("bbb\n".to_string()), ];
let (idx, local) = find_child_for_byte(&children, 4);
assert_eq!(idx, 0, "exact end of child 0 should stay in child 0");
assert_eq!(local, 4);
}
#[test]
fn split_children_many_children() {
let children: Vec<Arc<RopeNode>> = (0..B_MAX * 3)
.map(|i| RopeNode::new_leaf(format!("c{i}\n")))
.collect();
let result = split_children(&children);
assert!(result.len() > 1);
let total_bytes: usize = result.iter().map(|n| n.metrics.byte_len).sum();
let expected_bytes: usize = children.iter().map(|n| n.metrics.byte_len).sum();
assert_eq!(total_bytes, expected_bytes);
}
#[test]
fn split_children_just_over_bmax() {
let children: Vec<Arc<RopeNode>> = (0..=B_MAX)
.map(|i| RopeNode::new_leaf(format!("c{i}\n")))
.collect();
let result = split_children(&children);
assert_eq!(result.len(), 2, "should split into 2 groups");
}
#[test]
fn fixup_alignment_both_leaves() {
let left = RopeNode::new_leaf("hello".to_string()); let right = RopeNode::new_leaf(" world\n".to_string());
let mut children = vec![left, right];
fixup_alignment(&mut children);
let total: String = children
.iter()
.map(|n| {
let mut s = String::new();
collect_text(n, &mut s);
s
})
.collect();
assert_eq!(total, "hello world\n");
}
#[test]
fn fixup_alignment_with_internal_child() {
let left = RopeNode::new_internal(vec![
RopeNode::new_leaf("part1\n".to_string()),
RopeNode::new_leaf("part2".to_string()), ]);
let right = RopeNode::new_leaf(" more\n".to_string());
let mut children = vec![left, right];
fixup_alignment(&mut children);
let total: String = children
.iter()
.map(|n| {
let mut s = String::new();
collect_text(n, &mut s);
s
})
.collect();
assert_eq!(total, "part1\npart2 more\n");
}
#[test]
fn fixup_alignment_many_leaves_from_merge() {
use std::fmt::Write;
let mut left_text = String::new();
for i in 0..100 {
writeln!(left_text, "left line {i:04}").unwrap();
}
left_text.pop();
let mut right_text = String::new();
for i in 0..100 {
writeln!(right_text, "right line {i:04}").unwrap();
}
let left = RopeNode::new_leaf(left_text.clone());
let right = RopeNode::new_leaf(right_text.clone());
let mut children = vec![left, right];
fixup_alignment(&mut children);
let total: String = children
.iter()
.map(|n| {
let mut s = String::new();
collect_text(n, &mut s);
s
})
.collect();
assert_eq!(total, format!("{left_text}{right_text}"));
}
#[test]
fn mutated_rope_line_at_deep_child() {
let (mut r, _) = make_huge_rope();
for i in 0..20 {
let pos = i * 1000;
r = r.insert(pos.min(r.byte_len()), &format!("INSERT{i}\n"));
}
for i in (0..r.line_count()).step_by(500) {
assert!(r.line(i).is_some(), "line {i} should exist after mutations");
}
}
#[test]
fn mutated_rope_pos_to_byte_deep_child() {
let (mut r, _) = make_huge_rope();
for i in 0..10 {
r = r.insert(i * 500, "X\n");
}
for line_idx in (0..r.line_count()).step_by(1000) {
let byte = r.position_to_byte(line_idx, 0);
let (l, c) = r.byte_to_position(byte);
assert_eq!((l, c), (line_idx, 0), "roundtrip failed for line {line_idx}");
}
}
#[test]
fn remove_range_empty_on_leaf() {
let leaf = RopeNode::new_leaf("abc\ndef\n".to_string());
let result = remove_range(&leaf, 3..3);
assert_eq!(result.len(), 1);
if let NodeKind::Leaf { text } = &result[0].kind {
assert_eq!(text, "abc\ndef\n");
}
}
#[test]
fn insert_past_end_of_big_rope() {
let (r, text) = make_big_rope();
let r2 = r.insert(text.len() + 10000, "PAST_END");
assert!(r2.content().ends_with("PAST_END"));
assert_eq!(r2.byte_len(), text.len() + 8);
}
#[test]
fn build_tree_large_leaf_count() {
let leaves: Vec<Arc<RopeNode>> = (0..100)
.map(|i| RopeNode::new_leaf(format!("L{i:03}\n")))
.collect();
let root = build_tree(leaves);
assert!(!root.is_leaf());
assert_eq!(root.metrics.line_count, 100);
let mut content = String::new();
collect_text(&root, &mut content);
for i in 0..100 {
assert!(content.contains(&format!("L{i:03}\n")));
}
}
#[test]
fn line_at_internal_node_past_all_children() {
let internal = RopeNode::new_internal(vec![
RopeNode::new_leaf("hello\n".to_string()), RopeNode::new_leaf("world".to_string()), ]);
assert_eq!(line_at(&internal, 5), None);
assert_eq!(line_at(&internal, 2), None);
}
#[test]
fn chunk_text_empty_string() {
let chunks = chunk_text("");
assert!(chunks.is_empty(), "chunk_text of empty string should return empty vec");
}
#[test]
fn fixup_alignment_skips_when_left_ends_with_newline() {
let left = RopeNode::new_leaf("hello\n".to_string());
let right = RopeNode::new_leaf("world".to_string());
let mut children = vec![left, right];
fixup_alignment(&mut children);
assert_eq!(children.len(), 2, "aligned children should not be merged");
if let NodeKind::Leaf { text } = &children[0].kind {
assert_eq!(text, "hello\n");
}
if let NodeKind::Leaf { text } = &children[1].kind {
assert_eq!(text, "world");
}
}
#[test]
fn fixup_alignment_skips_when_left_is_empty() {
let left = RopeNode::new_leaf(String::new());
let right = RopeNode::new_leaf("world".to_string());
let mut children = vec![left, right];
fixup_alignment(&mut children);
assert_eq!(children.len(), 2, "empty left should not trigger merge");
}
#[test]
fn eq_unequal_ropes_same_bytelen_different_chunk_layout() {
let left = Rope {
root: RopeNode::new_internal(vec![
RopeNode::new_leaf("ab".to_string()),
RopeNode::new_leaf("cd".to_string()),
]),
};
let right = Rope {
root: RopeNode::new_internal(vec![
RopeNode::new_leaf("ax".to_string()),
RopeNode::new_leaf("cd".to_string()),
]),
};
assert_ne!(left, right, "different content should not be equal");
}
#[test]
fn fixup_alignment_left_internal_right_internal() {
let left = RopeNode::new_internal(vec![
RopeNode::new_leaf("aaa\n".to_string()),
RopeNode::new_leaf("bbb".to_string()), ]);
let right = RopeNode::new_internal(vec![
RopeNode::new_leaf("ccc\n".to_string()),
RopeNode::new_leaf("ddd\n".to_string()),
]);
let mut children = vec![left, right];
fixup_alignment(&mut children);
let total: String = children
.iter()
.map(|n| {
let mut s = String::new();
collect_text(n, &mut s);
s
})
.collect();
assert_eq!(total, "aaa\nbbbccc\nddd\n");
}
#[test]
fn fixup_alignment_left_leaf_right_internal() {
let left = RopeNode::new_leaf("hello".to_string());
let right = RopeNode::new_internal(vec![
RopeNode::new_leaf(" world\n".to_string()),
RopeNode::new_leaf("more\n".to_string()),
]);
let mut children = vec![left, right];
fixup_alignment(&mut children);
let total: String = children
.iter()
.map(|n| {
let mut s = String::new();
collect_text(n, &mut s);
s
})
.collect();
assert_eq!(total, "hello world\nmore\n");
}
#[test]
fn remove_range_internal_no_overlap_with_any_child() {
let internal = RopeNode::new_internal(vec![
RopeNode::new_leaf("aaa\n".to_string()), RopeNode::new_leaf("bbb\n".to_string()), ]);
let result = remove_range(&internal, 8..10);
assert_eq!(result.len(), 1);
assert_eq!(result[0].metrics.byte_len, 8, "no bytes should be removed");
}
#[test]
fn floor_char_boundary_at_zero() {
assert_eq!(floor_char_boundary("abc", 0), 0);
}
#[test]
fn fixup_alignment_two_internals_large_merge() {
let left_text = "a".repeat(600); let right_text = format!("\n{}", "b".repeat(600));
let left = RopeNode::new_internal(vec![RopeNode::new_leaf(left_text)]);
let right = RopeNode::new_internal(vec![RopeNode::new_leaf(right_text)]);
let mut children = vec![left, right];
fixup_alignment(&mut children);
let mut text = String::new();
for c in &children {
collect_text(c, &mut text);
}
assert_eq!(text.len(), 1201);
assert!(text.starts_with("aaa"));
assert!(text.contains('\n'));
}
#[test]
fn fixup_alignment_leaf_and_internal_large_merge() {
let left_text = "x".repeat(600);
let right_text = format!("\n{}", "y".repeat(600));
let left = RopeNode::new_leaf(left_text);
let right = RopeNode::new_internal(vec![RopeNode::new_leaf(right_text)]);
let mut children = vec![left, right];
fixup_alignment(&mut children);
let mut text = String::new();
for c in &children {
collect_text(c, &mut text);
}
assert_eq!(text.len(), 1201);
}
#[test]
#[should_panic(expected = "remove start not on char boundary")]
fn remove_range_panics_on_non_char_start_boundary() {
let leaf = RopeNode::new_leaf("α".to_string());
remove_range(&leaf, 1..2);
}
#[test]
#[should_panic(expected = "remove end not on char boundary")]
fn remove_range_panics_on_non_char_end_boundary() {
let leaf = RopeNode::new_leaf("αβ".to_string());
remove_range(&leaf, 0..3);
}