use pdfrum_doc::vt::{Config, Metrics};
use pdfrum_form::edit::ops::{self, TextEdit, insert_char, replace_selection};
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 multiline_config() -> Config {
Config {
plate: kurbo::Rect::new(0.0, 0.0, 1000.0, 200.0),
font_size: 1.0,
multi_line: true,
auto_return: true,
..Config::default()
}
}
fn edit(text: &str) -> TextEdit {
TextEdit::new(text, &config(), &metrics(), true)
}
fn typed(count: u32) -> TextEdit {
let mut edit = edit("");
for i in 0..count {
let ch = char::from_u32(u32::from(b'A') + i).unwrap_or('?');
insert_char(&mut edit, &config(), &metrics(), ch, None);
}
edit
}
const FIFTY: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr";
#[test]
fn the_helper_reproduces_the_upstream_fifty_character_run() {
assert_eq!(typed(50).text, FIFTY);
}
#[test]
fn typing_into_an_empty_field_appends_in_order() {
let mut edit = edit("");
assert!(edit.text.is_empty());
for ch in "abc".chars() {
insert_char(&mut edit, &config(), &metrics(), ch, None);
}
assert_eq!(edit.text, "abc");
}
#[test]
fn a_selection_over_an_empty_field_selects_nothing() {
let mut edit = edit("");
edit.set_selection(0, 3);
assert_eq!(edit.selected_text(), "");
for ch in "abc".chars() {
insert_char(&mut edit, &config(), &metrics(), ch, None);
}
edit.set_selection(0, 2);
assert_eq!(edit.selected_text(), "ab");
}
#[test]
fn the_signed_selection_indices_mean_three_different_things() {
let mut edit = typed(50);
edit.set_selection(0, 0);
assert_eq!(edit.selected_text(), "");
edit.set_selection(0, 1);
assert_eq!(edit.selected_text(), "A");
edit.set_selection(0, -1);
assert_eq!(edit.selected_text(), FIFTY);
edit.set_selection(-8, -1);
assert_eq!(edit.selected_text(), "");
edit.set_selection(23, 12);
assert_eq!(edit.selected_text(), "MNOPQRSTUVW");
edit.set_selection(12, 23);
assert_eq!(edit.selected_text(), "MNOPQRSTUVW");
edit.set_selection(49, 50);
assert_eq!(edit.selected_text(), "r");
edit.set_selection(49, 55);
assert_eq!(edit.selected_text(), "r");
}
#[test]
fn replacing_the_whole_selection_with_nothing_empties_the_field() {
let mut edit = typed(50);
edit.set_selection(0, -1);
assert_eq!(edit.selected_text(), FIFTY);
replace_selection(&mut edit, &config(), &metrics(), "", None);
assert!(edit.text.is_empty());
}
#[test]
fn replacing_an_empty_selection_with_nothing_changes_nothing() {
let mut edit = typed(50);
edit.set_selection(0, 0);
replace_selection(&mut edit, &config(), &metrics(), "", None);
assert_eq!(edit.text, FIFTY);
}
#[test]
fn a_selected_run_is_removed_from_either_end_or_the_middle() {
for (from, to, expected) in [
(0, 5, "FGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr"),
(25, 30, "ABCDEFGHIJKLMNOPQRSTUVWXY_`abcdefghijklmnopqr"),
(45, 50, "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklm"),
] {
let mut edit = typed(50);
edit.set_selection(from, to);
replace_selection(&mut edit, &config(), &metrics(), "", None);
assert_eq!(edit.text, expected, "removing [{from}, {to})");
}
}
#[test]
fn replacing_and_keeping_the_selection_reselects_the_replacement() {
let mut edit = typed(10);
edit.set_selection(1, 3);
assert_eq!(edit.text, "ABCDEFGHIJ");
ops::replace_and_keep_selection(&mut edit, &config(), &metrics(), "xyz", None);
assert_eq!(edit.text, "AxyzDEFGHIJ");
assert_eq!(edit.selected_text(), "xyz");
assert_eq!(edit.selection_indices(), (1, 4));
edit.set_selection(4, 1);
ops::replace_and_keep_selection(&mut edit, &config(), &metrics(), "12", None);
assert_eq!(edit.text, "A12DEFGHIJ");
assert_eq!(edit.selected_text(), "12");
assert_eq!(edit.selection_indices(), (1, 3));
}
#[test]
fn a_single_line_field_drops_a_trailing_line_break() {
for input in ["Foo\n", "Foo\r", "Foo\n\r", "Foo\r\n"] {
assert_eq!(
edit(input).text,
"Foo",
"a single-line field must drop the break in {input:?}"
);
}
}
#[test]
fn a_single_line_field_removes_an_interior_line_break() {
for input in ["Foo\nBar", "Foo\rBar", "Foo\n\rBar", "Foo\r\nBar"] {
assert_eq!(
edit(input).text,
"FooBar",
"a single-line field must join across the break in {input:?}"
);
}
}
#[test]
fn a_multiline_field_keeps_an_interior_line_break() {
let edit = TextEdit::new("Foo\nBar", &multiline_config(), &metrics(), false);
assert!(
edit.text.contains('\n'),
"a multiline field keeps the break: {:?}",
edit.text
);
}
#[test]
fn the_text_and_the_layout_never_disagree_about_length() {
for input in [
"Foo\nBar",
"Foo\r\nBar",
"\n\n\n",
"a\tb",
"plain",
"",
"trailing\n",
] {
let edit = edit(input);
let laid_out: usize = edit
.layout
.sections
.iter()
.map(|section| section.words.len())
.sum();
assert_eq!(
edit.len_chars(),
laid_out,
"text and layout disagree for {input:?}: {:?}",
edit.text
);
}
}