use super::*; use ntest::timeout; mod command_list; fn small_test_rope() -> Rc { Rope::join( Rope::join( Rope::join(Rope::new("abc"), Some(Rope::new("def"))), Some(Rope::join(Rope::new("g"), Some(Rope::new("hi")))), ), Some(Rope::join( Rope::join( Rope::join(Rope::new("jklm"), Some(Rope::new("n"))), Some(Rope::join(Rope::new("op"), Some(Rope::new("qr")))), ), Some(Rope::join(Rope::new("stuv"), Some(Rope::new("wxyz")))), )), ) } fn small_test_rope_leaf_strings() -> Vec<&'static str> { vec![ "abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz", ] } fn small_test_rope_full_string() -> &'static str { "abcdefghijklmnopqrstuvwxyz" } fn small_test_rope_with_multibyte_chars() -> Rc { Rope::join( Rope::join( Rope::join(Rope::new("aあbc"), Some(Rope::new("deえf"))), Some(Rope::join(Rope::new("g"), Some(Rope::new("hiい")))), ), Some(Rope::join( Rope::join( Rope::join(Rope::new("jklm"), Some(Rope::new("n"))), Some(Rope::join(Rope::new("おop"), Some(Rope::new("qr")))), ), Some(Rope::join(Rope::new("stuうv"), Some(Rope::new("wxyz")))), )), ) } fn small_test_rope_with_multibyte_chars_leaf_strings() -> Vec<&'static str> { vec![ "aあbc", "deえf", "g", "hiい", "jklm", "n", "おop", "qr", "stuうv", "wxyz", ] } fn small_test_rope_with_multibyte_chars_full_string() -> &'static str { "aあbcdeえfghiいjklmnおopqrstuうvwxyz" } impl Rope { fn unwrap_branch_left(&self) -> &Rc { match self { Rope::Branch { left, .. } => left, Rope::Leaf { .. } => panic!("Expected branch node but found leaf"), } } fn unwrap_branch_right(&self) -> Option<&Rc> { match self { Rope::Branch { right, .. } => right.as_ref(), Rope::Leaf { .. } => panic!("Expected branch node but found leaf"), } } } #[test] #[timeout(100)] fn node_iterator_for_single_node_returns_node_and_only_node() { let target = Rope::new("The"); let result: Vec<_> = target.iter_nodes().collect(); assert_eq!(result.len(), 1); assert_eq!(result[0].total_bytes(), 3); assert_eq!(result[0].total_chars(), 3); assert_eq!(result[0].total_lines(), 1); match result[0].as_ref() { Rope::Leaf { text, .. } => assert_eq!(text, "The"), _ => panic!(), } } #[test] #[timeout(100)] fn node_iterator_returns_nodes_in_correct_order() { let strings = small_test_rope_leaf_strings(); let target = small_test_rope(); let result: Vec<_> = target.iter_nodes().take(26).collect(); assert_eq!(result.len(), 10); for (node, string) in result.iter().zip(strings) { match &node.as_ref() { Rope::Leaf { text } => assert_eq!(text, string), _ => panic!(), } } } #[test] #[timeout(100)] fn char_iterator_returns_chars_in_correct_order() { let target = small_test_rope(); let result: Vec<_> = target.iter_chars().collect(); assert_eq!(result.len(), 26); for (&target_char, expected_char) in result.iter().zip(small_test_rope_full_string().chars()) { assert_eq!(target_char, expected_char); } } #[test] #[timeout(100)] fn split_splits_at_correct_location() { let target = small_test_rope(); let full_string = small_test_rope_full_string(); for i in 0..(full_string.chars().count()) { let (first, second) = target.split_at_char_index(i); let first_string: String = first.iter_chars().collect(); let second_string: String = second.iter_chars().collect(); assert_eq!(first_string, full_string[0..i]); assert_eq!(second_string, full_string[i..]); } } #[test] #[timeout(100)] fn split_splits_at_correct_location_with_multibyte_chars() { let target = small_test_rope_with_multibyte_chars(); let expected_chars: Vec<_> = small_test_rope_with_multibyte_chars_full_string() .chars() .collect(); for i in 0..(expected_chars.len()) { let (first, second) = target.split_at_char_index(i); let string: String = first.iter_chars().collect(); let expected: String = expected_chars[0..i].iter().collect(); assert_eq!(string, expected); let string: String = second.iter_chars().collect(); let expected: String = expected_chars[i..].iter().collect(); assert_eq!(string, expected); } } #[test] fn depths_have_correct_values() { let target = small_test_rope_with_multibyte_chars(); assert_eq!(target.depth(), 4); assert_eq!(target.unwrap_branch_left().depth(), 2); assert_eq!(target.unwrap_branch_left().unwrap_branch_left().depth(), 1); assert_eq!( target .unwrap_branch_left() .unwrap_branch_left() .unwrap_branch_left() .depth(), 0 ); assert_eq!( target .unwrap_branch_left() .unwrap_branch_left() .unwrap_branch_right() .unwrap() .depth(), 0 ); assert_eq!( target .unwrap_branch_left() .unwrap_branch_right() .unwrap() .depth(), 1 ); assert_eq!( target .unwrap_branch_left() .unwrap_branch_right() .unwrap() .unwrap_branch_left() .depth(), 0 ); assert_eq!( target .unwrap_branch_left() .unwrap_branch_right() .unwrap() .unwrap_branch_right() .unwrap() .depth(), 0 ); assert_eq!(target.unwrap_branch_right().unwrap().depth(), 3); assert_eq!( target .unwrap_branch_right() .unwrap() .unwrap_branch_left() .depth(), 2 ); assert_eq!( target .unwrap_branch_right() .unwrap() .unwrap_branch_right() .unwrap() .depth(), 1 ); } #[test] fn insert_at_char_index() { let target = Rope::new("The brown dog"); assert_eq!(target.iter_chars().collect::(), "The brown dog"); let rope1 = target.insert_at_char_index(4, "quick"); assert_eq!(target.iter_chars().collect::(), "The brown dog"); assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); let rope2 = rope1.insert_at_char_index(9, " "); assert_eq!(target.iter_chars().collect::(), "The brown dog"); assert_eq!(rope1.iter_chars().collect::(), "The quickbrown dog"); assert_eq!( rope2.iter_chars().collect::(), "The quick brown dog" ); let rope3 = rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox."); assert_eq!( rope3.iter_chars().collect::(), "The quick brown dog jumps over the lazy fox." ); } #[test] fn delete_at_char_index() { let target = Rope::new("The quick brown fox jumps over the lazy dog."); let test = target.delete_at_char_index(10, 6); assert_eq!( target.iter_chars().collect::(), "The quick brown fox jumps over the lazy dog." ); assert_eq!( test.iter_chars().collect::(), "The quick fox jumps over the lazy dog." ); let test = target.delete_at_char_index(0, 4); assert_eq!( target.iter_chars().collect::(), "The quick brown fox jumps over the lazy dog." ); assert_eq!( test.iter_chars().collect::(), "quick brown fox jumps over the lazy dog." ); let test = target.delete_at_char_index("The quick brown fox jumps over the lazy dog.".len() - 5, 5); assert_eq!( target.iter_chars().collect::(), "The quick brown fox jumps over the lazy dog." ); assert_eq!( test.iter_chars().collect::(), "The quick brown fox jumps over the lazy" ); } #[test] fn char_iterator_reports_correct_point() { // Build a rope from fragments so we get a proper tree structure. let target = Rope::new("This") .concat(Rope::new(" is the first ")) .concat(Rope::new("line.\n")) .concat(Rope::new("This is the ")) .concat(Rope::new("second line.\nThis is the third line.")) .concat(Rope::new("\n")) .concat(Rope::new( "This is the fourth line.\nThis is the fifth line.\nThis is ", )) .concat(Rope::new("the")) .concat(Rope::new(" sixth")) .concat(Rope::new(" line.")) .concat(Rope::new("\nThis")) .concat(Rope::new(" is the seventh line.")); let expected_values = vec![ ('T', 0, 1), ('h', 0, 2), ('i', 0, 3), ('s', 0, 4), (' ', 0, 5), ('i', 0, 6), ('s', 0, 7), (' ', 0, 8), ('t', 0, 9), ('h', 0, 10), ('e', 0, 11), (' ', 0, 12), ('f', 0, 13), ('i', 0, 14), ('r', 0, 15), ('s', 0, 16), ('t', 0, 17), (' ', 0, 18), ('l', 0, 19), ('i', 0, 20), ('n', 0, 21), ('e', 0, 22), ('.', 0, 23), ('\n', 0, 24), ('T', 1, 1), ('h', 1, 2), ('i', 1, 3), ('s', 1, 4), (' ', 1, 5), ('i', 1, 6), ('s', 1, 7), (' ', 1, 8), ('t', 1, 9), ('h', 1, 10), ('e', 1, 11), (' ', 1, 12), ('s', 1, 13), ('e', 1, 14), ('c', 1, 15), ('o', 1, 16), ('n', 1, 17), ('d', 1, 18), (' ', 1, 19), ('l', 1, 20), ('i', 1, 21), ('n', 1, 22), ('e', 1, 23), ('.', 1, 24), ('\n', 1, 25), ('T', 2, 1), ('h', 2, 2), ('i', 2, 3), ('s', 2, 4), (' ', 2, 5), ('i', 2, 6), ('s', 2, 7), (' ', 2, 8), ('t', 2, 9), ('h', 2, 10), ('e', 2, 11), (' ', 2, 12), ('t', 2, 13), ('h', 2, 14), ('i', 2, 15), ('r', 2, 16), ('d', 2, 17), (' ', 2, 18), ('l', 2, 19), ('i', 2, 20), ('n', 2, 21), ('e', 2, 22), ('.', 2, 23), ('\n', 2, 24), ('T', 3, 1), ('h', 3, 2), ('i', 3, 3), ('s', 3, 4), (' ', 3, 5), ('i', 3, 6), ('s', 3, 7), (' ', 3, 8), ('t', 3, 9), ('h', 3, 10), ('e', 3, 11), (' ', 3, 12), ('f', 3, 13), ('o', 3, 14), ('u', 3, 15), ('r', 3, 16), ('t', 3, 17), ('h', 3, 18), (' ', 3, 19), ('l', 3, 20), ('i', 3, 21), ('n', 3, 22), ('e', 3, 23), ('.', 3, 24), ('\n', 3, 25), ('T', 4, 1), ('h', 4, 2), ('i', 4, 3), ('s', 4, 4), (' ', 4, 5), ('i', 4, 6), ('s', 4, 7), (' ', 4, 8), ('t', 4, 9), ('h', 4, 10), ('e', 4, 11), (' ', 4, 12), ('f', 4, 13), ('i', 4, 14), ('f', 4, 15), ('t', 4, 16), ('h', 4, 17), (' ', 4, 18), ('l', 4, 19), ('i', 4, 20), ('n', 4, 21), ('e', 4, 22), ('.', 4, 23), ('\n', 4, 24), ('T', 5, 1), ('h', 5, 2), ('i', 5, 3), ('s', 5, 4), (' ', 5, 5), ('i', 5, 6), ('s', 5, 7), (' ', 5, 8), ('t', 5, 9), ('h', 5, 10), ('e', 5, 11), (' ', 5, 12), ('s', 5, 13), ('i', 5, 14), ('x', 5, 15), ('t', 5, 16), ('h', 5, 17), (' ', 5, 18), ('l', 5, 19), ('i', 5, 20), ('n', 5, 21), ('e', 5, 22), ('.', 5, 23), ('\n', 5, 24), ('T', 6, 1), ('h', 6, 2), ('i', 6, 3), ('s', 6, 4), (' ', 6, 5), ('i', 6, 6), ('s', 6, 7), (' ', 6, 8), ('t', 6, 9), ('h', 6, 10), ('e', 6, 11), (' ', 6, 12), ('s', 6, 13), ('e', 6, 14), ('v', 6, 15), ('e', 6, 16), ('n', 6, 17), ('t', 6, 18), ('h', 6, 19), (' ', 6, 20), ('l', 6, 21), ('i', 6, 22), ('n', 6, 23), ('e', 6, 24), ('.', 6, 25), ('\n', 7, 26), ]; for (found, (expected_char, expected_line, expected_column)) in target.iter_chars_with_point().zip(expected_values) { assert_eq!(found.character, expected_char); assert_eq!(found.line, expected_line); assert_eq!(found.column, expected_column); } } // Run multiple tests with different seeds so that `cargo test` will run them in // parallel. // TODO: This could use a macro to generate the list of tests. fn random_insertions_and_deletions_with_seed(seed: u64) { let (start_text, edits) = command_list::generate_random_edit_sequence_with_seed(1000, seed); let mut target = Rope::new(start_text); for (command, expected_text) in edits { println!("{:?}", command); target = command.run(&target); assert_eq!(expected_text, target.iter_chars().collect::()); } } #[test] fn random_insertions_and_deletions_1() { random_insertions_and_deletions_with_seed(0x1234567890ab0001); } #[test] fn random_insertions_and_deletions_2() { random_insertions_and_deletions_with_seed(0x1234567890ab0002); } #[test] fn random_insertions_and_deletions_3() { random_insertions_and_deletions_with_seed(0x1234567890ab0003); } #[test] fn random_insertions_and_deletions_4() { random_insertions_and_deletions_with_seed(0x1234567890ab0004); } #[test] fn random_insertions_and_deletions_5() { random_insertions_and_deletions_with_seed(0x1234567890ab0005); } #[test] fn random_insertions_and_deletions_6() { random_insertions_and_deletions_with_seed(0x1234567890ab0006); } #[test] fn random_insertions_and_deletions_7() { random_insertions_and_deletions_with_seed(0x1234567890ab0007); } #[test] fn random_insertions_and_deletions_8() { random_insertions_and_deletions_with_seed(0x1234567890ab0008); } #[test] fn random_insertions_and_deletions_9() { random_insertions_and_deletions_with_seed(0x1234567890ab0009); } #[test] fn random_insertions_and_deletions_10() { random_insertions_and_deletions_with_seed(0x1234567890ab0010); } #[test] fn random_insertions_and_deletions_11() { random_insertions_and_deletions_with_seed(0x1234567890ab0011); } #[test] fn random_insertions_and_deletions_12() { random_insertions_and_deletions_with_seed(0x1234567890ab0012); } #[test] fn random_insertions_and_deletions_13() { random_insertions_and_deletions_with_seed(0x1234567890ab0013); } #[test] fn random_insertions_and_deletions_14() { random_insertions_and_deletions_with_seed(0x1234567890ab0014); } #[test] fn random_insertions_and_deletions_15() { random_insertions_and_deletions_with_seed(0x1234567890ab0015); } #[test] fn random_insertions_and_deletions_16() { random_insertions_and_deletions_with_seed(0x1234567890ab0016); }