use pdfrum_doc::vt::{self, Config, Metrics};
use pdfrum_form::edit::ops::{
self, TextEdit, backspace, delete, insert_char, replace_and_keep_selection, replace_selection,
};
use pdfrum_form::edit::{Selection, place::PlaceExt};
fn metrics() -> Metrics<'static> {
Metrics {
width: &|_| 1000,
ascent: 800,
descent: -200,
}
}
fn config() -> Config {
Config {
plate: kurbo::Rect::new(0.0, 0.0, 1000.0, 20.0),
font_size: 1.0,
..Config::default()
}
}
fn edit(text: &str) -> TextEdit {
TextEdit::new(text, &config(), &metrics(), true)
}
fn type_text(edit: &mut TextEdit, text: &str, max_len: Option<u32>) {
for ch in text.chars() {
insert_char(edit, &config(), &metrics(), ch, max_len);
}
}
fn select(edit: &mut TextEdit, from: usize, to: usize) {
let begin = vt::hit::place_of_word_index(&edit.layout, from);
let end = vt::hit::place_of_word_index(&edit.layout, to);
edit.selection = Selection::new(begin, end);
edit.caret = end;
}
#[test]
fn typing_appends_characters_in_order() {
let mut field = edit("");
type_text(&mut field, "ABCDE", None);
assert_eq!(field.text, "ABCDE");
assert_eq!(field.caret_index(), 5);
}
#[test]
fn typing_undoes_one_character_at_a_time() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
assert!(!field.undo.can_undo());
type_text(&mut field, "ABCDE", None);
assert_eq!(field.text, "ABCDE");
assert!(field.undo.can_undo());
assert!(!field.undo.can_redo());
assert!(ops::undo(&mut field, &config, &metrics));
assert_eq!(field.text, "ABCD");
assert!(ops::undo(&mut field, &config, &metrics));
assert_eq!(field.text, "ABC");
assert!(field.undo.can_redo());
assert!(ops::redo(&mut field, &config, &metrics));
assert_eq!(field.text, "ABCD");
assert!(ops::redo(&mut field, &config, &metrics));
assert_eq!(field.text, "ABCDE");
assert!(field.undo.can_undo());
assert!(!field.undo.can_redo());
}
#[test]
fn undoing_every_step_empties_the_field() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
type_text(&mut field, "ABC", None);
for _ in 0..3 {
assert!(ops::undo(&mut field, &config, &metrics));
}
assert_eq!(field.text, "");
assert!(!field.undo.can_undo());
assert!(!ops::undo(&mut field, &config, &metrics));
}
#[test]
fn a_multi_character_replace_undoes_in_one_step() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
assert!(replace_and_keep_selection(
&mut field, &config, &metrics, "UVW", None
));
assert_eq!(field.text, "UVW");
assert!(ops::undo(&mut field, &config, &metrics));
assert_eq!(field.text, "");
assert!(!field.undo.can_undo());
}
#[test]
fn replace_and_keep_selection_keeps_it_and_undo_restores_the_old_one() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
type_text(&mut field, "AB", None);
select(&mut field, 0, 1);
assert_eq!(field.selected_text(), "A");
assert!(replace_and_keep_selection(
&mut field, &config, &metrics, "XYZ", None
));
assert_eq!(field.text, "XYZB");
assert_eq!(field.selected_text(), "XYZ", "the insertion stays selected");
assert!(ops::undo(&mut field, &config, &metrics));
assert_eq!(field.text, "AB");
assert_eq!(
field.selected_text(),
"A",
"undo restores the pre-edit selection"
);
assert!(ops::redo(&mut field, &config, &metrics));
assert_eq!(field.text, "XYZB");
assert_eq!(
field.selected_text(),
"",
"redo does not restore it: the selection is not an undo item"
);
}
#[test]
fn replace_selection_leaves_no_selection() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
type_text(&mut field, "AB", None);
select(&mut field, 0, 1);
assert!(replace_selection(
&mut field, &config, &metrics, "XYZ", None
));
assert_eq!(field.text, "XYZB");
assert_eq!(field.selected_text(), "");
}
#[test]
fn a_cut_of_everything_undoes_in_one_step() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
type_text(&mut field, "ABC", None);
field.select_all();
assert_eq!(field.selected_text(), "ABC");
assert!(replace_selection(&mut field, &config, &metrics, "", None));
assert_eq!(field.text, "");
assert!(ops::undo(&mut field, &config, &metrics));
assert_eq!(field.text, "ABC", "the cut is atomic");
assert!(
field.undo.can_undo(),
"the three characters are still below"
);
}
#[test]
fn undoing_a_select_all_cut_restores_the_selection() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
type_text(&mut field, "AB", None);
field.select_all();
replace_selection(&mut field, &config, &metrics, "", None);
assert_eq!(field.text, "");
ops::undo(&mut field, &config, &metrics);
assert_eq!(field.text, "AB");
assert_eq!(field.selected_text(), "AB");
}
#[test]
fn select_all_on_an_empty_field_selects_nothing() {
let mut field = edit("");
field.select_all();
assert_eq!(field.selected_text(), "");
let mut hello = edit("Hello");
hello.select_all();
assert_eq!(hello.selected_text(), "Hello");
}
#[test]
fn backspace_and_delete_take_opposite_sides_of_the_caret() {
let (config, metrics) = (config(), metrics());
let mut field = edit("ABCDE");
field.set_caret_index(3);
assert!(backspace(&mut field, &config, &metrics));
assert_eq!(field.text, "ABDE");
let mut field = edit("ABCDE");
field.set_caret_index(3);
assert!(delete(&mut field, &config, &metrics));
assert_eq!(field.text, "ABCE");
}
#[test]
fn deleting_past_either_end_does_nothing() {
let (config, metrics) = (config(), metrics());
let mut field = edit("AB");
field.set_caret_index(0);
assert!(!backspace(&mut field, &config, &metrics));
assert_eq!(field.text, "AB");
field.set_caret_index(2);
assert!(!delete(&mut field, &config, &metrics));
assert_eq!(field.text, "AB");
}
#[test]
fn deleting_with_a_selection_removes_the_selection() {
let (config, metrics) = (config(), metrics());
for forwards in [false, true] {
let mut field = edit("ABCDE");
select(&mut field, 1, 4);
assert_eq!(field.selected_text(), "BCD");
if forwards {
delete(&mut field, &config, &metrics);
} else {
backspace(&mut field, &config, &metrics);
}
assert_eq!(field.text, "AE");
assert_eq!(field.selected_text(), "");
}
}
const LIMIT: Option<u32> = Some(10);
fn elephant() -> TextEdit {
edit("Elephant")
}
#[test]
fn an_overlong_insert_into_an_empty_limited_field_is_truncated() {
let (config, metrics) = (config(), metrics());
let mut field = elephant();
field.select_all();
replace_selection(&mut field, &config, &metrics, "", LIMIT);
assert_eq!(field.text, "");
replace_selection(&mut field, &config, &metrics, "Hippopotamus", LIMIT);
assert_eq!(field.text, "Hippopotam");
}
#[test]
fn an_insert_into_a_populated_limited_field_takes_only_what_fits() {
let (config, metrics) = (config(), metrics());
let mut field = elephant();
field.set_caret_index(0);
replace_selection(&mut field, &config, &metrics, "Hippopotamus", LIMIT);
assert_eq!(field.text, "HiElephant");
let mut field = elephant();
field.set_caret_index(5);
replace_selection(&mut field, &config, &metrics, "Hippopotamus", LIMIT);
assert_eq!(field.text, "ElephHiant");
let mut field = elephant();
field.set_caret_index(8);
replace_selection(&mut field, &config, &metrics, "Hippopotamus", LIMIT);
assert_eq!(field.text, "ElephantHi");
}
#[test]
fn a_replaced_selection_frees_room_for_the_insertion() {
let (config, metrics) = (config(), metrics());
let mut field = elephant();
field.select_all();
replace_selection(&mut field, &config, &metrics, "Hippopotamus", LIMIT);
assert_eq!(field.text, "Hippopotam");
for (from, to, selected, expected) in [
(0, 4, "Elep", "Hippophant"),
(2, 6, "epha", "ElHippopnt"),
(4, 8, "hant", "ElepHippop"),
] {
let mut field = elephant();
select(&mut field, from, to);
assert_eq!(field.selected_text(), selected);
replace_selection(&mut field, &config, &metrics, "Hippopotamus", LIMIT);
assert_eq!(field.text, expected, "replacing {selected:?}");
}
}
#[test]
fn a_full_field_accepts_nothing_and_loses_nothing() {
let (config, metrics) = (config(), metrics());
let mut field = edit("ABCDEFGHIJ");
assert_eq!(field.len_chars(), 10);
field.set_caret_index(5);
insert_char(&mut field, &config, &metrics, 'Z', LIMIT);
assert_eq!(field.text, "ABCDEFGHIJ");
}
#[test]
fn the_last_free_character_is_accepted() {
let (config, metrics) = (config(), metrics());
let mut field = edit("ABCDEFGHI");
field.set_caret_index(9);
assert!(insert_char(&mut field, &config, &metrics, 'J', LIMIT));
assert_eq!(field.text, "ABCDEFGHIJ");
}
#[test]
fn a_field_with_no_limit_takes_everything() {
let (config, metrics) = (config(), metrics());
let mut field = edit("Elephant");
field.set_caret_index(8);
replace_selection(&mut field, &config, &metrics, "Hippopotamus", None);
assert_eq!(field.text, "ElephantHippopotamus");
}
#[test]
fn non_ascii_text_is_indexed_by_character() {
let (config, metrics) = (config(), metrics());
let mut field = edit("");
type_text(&mut field, "\u{05D1}\u{05D2}\u{05EA}", None);
assert_eq!(field.text, "\u{05D1}\u{05D2}\u{05EA}");
assert_eq!(field.len_chars(), 3);
assert_eq!(field.caret_index(), 3);
assert!(backspace(&mut field, &config, &metrics));
assert_eq!(field.text, "\u{05D1}\u{05D2}");
}
#[test]
fn the_caret_starts_at_the_line_header() {
let field = edit("ABC");
assert_eq!(field.caret_index(), 0);
assert!(field.caret.at_line_start());
}