#[macro_use] extern crate proptest; use proptest::collection::vec; use proptest::test_runner::Config; use ropey::Rope; use backend::change::{ApplyChange, Change}; fn make_edit_list_valid(edits: &mut Vec<(usize, usize, String)>, start_text: &str) { edits.sort_by_key(|e| e.0); let mut tail = 0; for e in edits.iter_mut() { e.0 = e.0.max(tail); while e.0 < start_text.len() && !start_text.is_char_boundary(e.0) { e.0 += 1; } while (e.0 + e.1) < start_text.len() && !start_text.is_char_boundary(e.0 + e.1) { e.1 += 1; } tail = e.0 + e.1; } for i in (0..edits.len()).rev() { if edits[i].0 > start_text.len() { edits.remove(i); } else if (edits[i].0 + edits[i].1) > start_text.len() { edits[i].1 = start_text.len() - edits[i].0; } } } proptest! { #![proptest_config(Config::with_cases(512))] #[test] fn compose( ref start_text in "\\PC{0, 200}", mut edits1 in vec((0usize..100, 0usize..10, "\\PC{0, 10}"), 0..5), mut edits2 in vec((0usize..100, 0usize..10, "\\PC{0, 10}"), 0..5), ) { // Make edits1 valid and compute the post-edits1 string. make_edit_list_valid(&mut edits1, start_text); let change1 = Change::from_ordered_edit_set(edits1.iter().map(|e| { (e.0, &start_text[e.0..(e.0+e.1)], &e.2[..]) })); let mut r1 = Rope::from_str(start_text); r1.apply_change(&change1); let text1: String = r1.clone().into(); // Make edits2 valid and compute the post-edits2 string. make_edit_list_valid(&mut edits2, &text1); let change2 = Change::from_ordered_edit_set(edits2.iter().map(|e| { (e.0, &text1[e.0..(e.0+e.1)], &e.2[..]) })); r1.apply_change(&change2); // Do the test! let change3 = change1.compose(&change2); let mut r2 = Rope::from_str(start_text); r2.apply_change(&change3); assert_eq!(r1, r2); } }