use proptest::prelude::*;
use smudge_core::reactive::prelude::*;
use super::{Editor, ParagraphTable, SNAPSHOTS};
fn cuts(table: &ParagraphTable) -> Vec<(usize, usize)> {
table
.paragraphs
.iter()
.map(|paragraph| (paragraph.start, paragraph.len))
.collect()
}
fn boundary(text: &str, byte: usize) -> usize {
let mut byte = byte.min(text.len());
while !text.is_char_boundary(byte) {
byte -= 1;
}
byte
}
fn text() -> impl Strategy<Value = String> {
proptest::collection::vec(prop_oneof!["a", "é", "\n", "\n\n"], 0..12)
.prop_map(|pieces| pieces.concat())
}
proptest! {
#[test]
fn a_splice_cuts_the_text_as_a_full_rebuild_does(
initial in text(),
edits in proptest::collection::vec((any::<usize>(), any::<usize>(), text()), 1..8),
) {
let mut buffer = initial;
let mut table = ParagraphTable::default();
table.rebuild(&buffer);
for (a, b, replacement) in edits {
let a = boundary(&buffer, a % (buffer.len() + 1));
let b = boundary(&buffer, b % (buffer.len() + 1));
let range = a.min(b)..a.max(b);
buffer.replace_range(range.clone(), &replacement);
table.splice(range, &replacement);
let mut rebuilt = ParagraphTable::default();
rebuilt.rebuild(&buffer);
prop_assert_eq!(cuts(&table), cuts(&rebuilt), "after editing {:?}", buffer);
}
}
}
#[test]
fn a_word_step_in_wrapped_mixed_direction_text_returns() {
let (done, finished) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let value = smudge_core::reactive::RwSignal::new(String::new());
let editor = super::Editor::new(value.clone());
editor.set_style(&crate::TextStyle::default());
editor.delete();
editor.move_up();
editor.compose("", None);
editor.move_up();
editor.delete();
editor.insert("ٿ� ((");
editor.insert("\u{7ba}Ȏ\u{61c}�");
editor.extend_up();
editor.delete();
editor.delete();
editor.insert("��������");
editor.insert("��������");
editor.insert("�T�");
editor.move_to_point(308.0, 392.0);
editor.extend_right();
editor.extend_right();
editor.extend_right();
editor.move_to_end();
editor.extend_right();
editor.extend_right();
{
value.set(String::from("ũ?"));
editor.set_text("ũ?");
};
editor.insert("�\"���");
editor.insert("�\u{311}\u{61c}��");
editor.extend_to_point(300.0, 468.0);
editor.insert("\n");
editor.insert("��*");
editor.extend_to_point(350.0, 188.0);
editor.move_down();
editor.move_lines(-8);
editor.insert("��𪪶\u{311}");
editor.insert("\u{61c}��:��A");
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.compose("", None);
editor.insert("\n");
editor.insert("��*");
editor.extend_to_point(56.0, -26.0);
editor.extend_up();
editor.move_up();
editor.move_word_right();
editor.extend_left();
editor.insert("����ũ?");
editor.move_word_left();
editor.delete_word();
editor.move_to_end();
editor.delete_word();
editor.select_word_at_point(188.0, 186.0);
editor.move_lines(-8);
editor.insert("��𪪶\u{311}");
editor.insert("\u{61c}:��");
editor.insert("������l");
editor.move_word_right();
editor.compose("", None);
editor.redo();
editor.insert("ffffff�");
editor.extend_right();
editor.backspace();
editor.backspace();
editor.backspace();
editor.backspace();
editor.backspace();
editor.backspace();
editor.backspace_word();
editor.backspace();
editor.extend_right();
editor.extend_right();
editor.set_wrap_width(Some(60.0));
editor.set_wrap_width(Some(60.0));
editor.move_word_right();
editor.compose("", None);
editor.redo();
editor.insert("ffffff�");
editor.extend_right();
editor.extend_right();
editor.move_word_right();
editor.extend_lines(0);
let _ = done.send(());
});
assert!(
finished
.recv_timeout(std::time::Duration::from_secs(30))
.is_ok(),
"the edits did not finish within 30 seconds"
);
}
#[test]
fn an_edit_that_changes_nothing_copies_no_text() {
let editor = Editor::new(smudge_core::reactive::RwSignal::new(String::new()));
editor.set_style(&crate::TextStyle::default());
editor.set_text("hello");
editor.move_to_start();
let before = SNAPSHOTS.with(std::cell::Cell::get);
editor.backspace();
editor.backspace_word();
editor.move_to_end();
editor.delete();
editor.delete_word();
editor.delete_selection();
editor.compose("\u{3042}", None);
editor.compose("", None);
assert_eq!(
SNAPSHOTS.with(std::cell::Cell::get),
before,
"edits that change no text copy no text for undo"
);
editor.insert("x");
assert_eq!(
SNAPSHOTS.with(std::cell::Cell::get),
before + 1,
"an edit that opens an undo step copies the text once"
);
}
#[test]
fn selection_rectangles_outside_the_visible_range_are_left_out() {
let editor = Editor::new(smudge_core::reactive::RwSignal::new(String::new()));
editor.set_style(&crate::TextStyle::default());
editor.set_text(&"line\n".repeat(200));
editor.select_all();
assert!(editor.selection_geometry().len() >= 200);
let mut rects = Vec::new();
editor.selection_rects((40.0, 80.0), &mut rects);
assert!(!rects.is_empty(), "the visible lines show their selection");
assert!(
rects.len() < 10,
"only the lines near the range are kept: {}",
rects.len()
);
assert!(
rects.iter().all(|rect| rect.y1 >= 40.0 && rect.y0 <= 80.0),
"every kept rectangle meets the range: {rects:?}"
);
}