use std::collections::BTreeSet;
use pdfrum_form::field::choice::{
initial_selection, is_index_selected, move_caret_by, select_only, set_index_selected,
top_visible_for, type_ahead,
};
use pdfrum_form::field::{ChoiceConfig, ChoiceOption, ChoiceState};
const FRUIT: [&str; 26] = [
"Apple",
"Banana",
"Cherry",
"Date",
"Elderberry",
"Fig",
"Guava",
"Honeydew",
"Indian Fig",
"Jackfruit",
"Kiwi",
"Lemon",
"Mango",
"Nectarine",
"Orange",
"Persimmon",
"Quince",
"Raspberry",
"Strawberry",
"Tamarind",
"Ugli Fruit",
"Voavanga",
"Wolfberry",
"Xigua",
"Yangmei",
"Zucchini",
];
const PROVINCES: [&str; 10] = [
"Alberta",
"British Columbia",
"Manitoba",
"New Brunswick",
"Newfoundland and Labrador",
"Nova Scotia",
"Ontario",
"Prince Edward Island",
"Quebec",
"Saskatchewan",
];
const LAST_SELECTED_RECT: (f32, f32, f32, f32) = (100.0, 100.0, 200.0, 130.0);
const LAST_SELECTED_FONT_SIZE: f32 = 12.0;
fn options(labels: &[&str]) -> Vec<ChoiceOption> {
labels
.iter()
.map(|l| ChoiceOption {
label: (*l).to_string(),
value: (*l).to_string(),
})
.collect()
}
fn multi_list(labels: &[&str]) -> ChoiceState {
ChoiceState::new(
options(labels),
ChoiceConfig {
multi_select: true,
..ChoiceConfig::default()
},
)
}
fn single_list(labels: &[&str]) -> ChoiceState {
ChoiceState::new(options(labels), ChoiceConfig::default())
}
fn combo(labels: &[&str]) -> ChoiceState {
ChoiceState::new(
options(labels),
ChoiceConfig {
combo: true,
..ChoiceConfig::default()
},
)
}
#[test]
fn a_multi_select_lists_focused_text_follows_the_last_row_acted_upon() {
let mut list = multi_list(&FRUIT);
for index in [5, 6, 20] {
assert!(set_index_selected(&mut list, index, true));
}
assert_eq!(
list.focused_text(),
"Ugli Fruit",
"the last row selected answers, not the first"
);
for index in [20, 1] {
set_index_selected(&mut list, index, false);
}
assert_eq!(list.focused_text(), "Banana");
assert!(!is_index_selected(&list, 3));
assert!(set_index_selected(&mut list, 3, false));
assert_eq!(list.focused_text(), "Date");
}
#[test]
fn a_list_does_not_overscroll_past_its_last_full_page() {
let mut list = single_list(&PROVINCES);
assert!(set_index_selected(&mut list, 9, true));
for i in 0..10 {
assert_eq!(is_index_selected(&list, i), i == 9);
}
let rect_height = LAST_SELECTED_RECT.3 - LAST_SELECTED_RECT.1;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let visible_rows = (rect_height / LAST_SELECTED_FONT_SIZE).floor() as usize;
assert_eq!(visible_rows, 2, "the fixture's own geometry shows two rows");
let top = top_visible_for(PROVINCES.len(), visible_rows, 9);
assert_eq!(top, 8, "scrolling to 9 would leave the box short");
assert!(select_only(&mut list, top));
for i in 0..10 {
assert_eq!(is_index_selected(&list, i), i == 8);
}
assert_eq!(list.focused_text(), "Quebec");
}
#[test]
fn the_value_entry_wins_over_the_index_entry() {
let greek: Vec<String> = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"]
.iter()
.map(|s| (*s).to_string())
.collect();
assert_eq!(
initial_selection(&greek, &[], &[1, 3]),
BTreeSet::from([1, 3])
);
assert_eq!(
initial_selection(&greek, &["Gamma".to_string()], &[]),
BTreeSet::from([2])
);
assert_eq!(
initial_selection(
&greek,
&["Gamma".to_string(), "Epsilon".to_string()],
&[0, 1]
),
BTreeSet::from([2, 4])
);
}
#[test]
fn a_combo_box_refuses_to_be_deselected() {
let mut combo = combo(&FRUIT);
assert!(set_index_selected(&mut combo, 1, true));
assert!(is_index_selected(&combo, 1));
assert!(
!set_index_selected(&mut combo, 1, false),
"a combo box has no empty state"
);
assert!(is_index_selected(&combo, 1), "and it did not clear");
}
#[test]
fn a_single_select_list_can_be_emptied() {
let mut list = single_list(&["Foo", "Bar", "Qux"]);
assert!(set_index_selected(&mut list, 1, true));
assert!(set_index_selected(&mut list, 1, false));
assert!(list.selected.is_empty(), "the list is genuinely empty");
for i in 0..3 {
assert!(!is_index_selected(&list, i));
}
}
#[test]
fn an_out_of_range_row_is_refused() {
for mut field in [multi_list(&FRUIT), single_list(&FRUIT), combo(&FRUIT)] {
assert!(set_index_selected(&mut field, 0, true));
let before = field.selected.clone();
assert!(!set_index_selected(&mut field, 100, true));
assert!(!set_index_selected(&mut field, FRUIT.len(), true));
assert_eq!(field.selected, before, "a refused call changed nothing");
assert!(!is_index_selected(&field, 100));
}
}
#[test]
fn combo_type_ahead_treats_each_character_as_its_own_jump() {
let mut field = combo(&FRUIT);
type_ahead(&mut field, 'A');
assert_eq!(field.focused_text(), "Apple");
for ch in ['A', 'B', 'C'] {
type_ahead(&mut field, ch);
}
assert_eq!(field.focused_text(), "Cherry");
for ch in ['A', 'B'] {
type_ahead(&mut field, ch);
}
assert_eq!(field.focused_text(), "Banana");
}
#[test]
fn multi_select_accumulates_where_single_select_replaces() {
let mut multi = multi_list(&FRUIT);
for index in [1, 5, 20] {
set_index_selected(&mut multi, index, true);
}
assert_eq!(multi.selected, BTreeSet::from([1, 5, 20]));
let mut single = single_list(&FRUIT);
for index in [1, 5, 20] {
set_index_selected(&mut single, index, true);
}
assert_eq!(
single.selected,
BTreeSet::from([20]),
"only the last stands"
);
}
#[test]
fn a_field_with_no_rows_refuses_every_index() {
let mut empty = single_list(&[]);
assert!(!set_index_selected(&mut empty, 0, true));
assert!(!set_index_selected(&mut empty, 100, true));
assert!(!is_index_selected(&empty, 0));
assert_eq!(empty.focused_text(), "");
}
#[test]
fn the_accelerator_moves_a_multi_selects_caret_without_its_selection() {
let mut list = multi_list(&FRUIT);
select_only(&mut list, 3);
assert_eq!(list.selected, BTreeSet::from([3]));
assert!(move_caret_by(&mut list, 1, false, true));
assert_eq!(list.caret_index, Some(4), "the caret moved");
assert_eq!(
list.selected,
BTreeSet::from([3]),
"and the selection did not"
);
}
#[test]
fn shift_ranges_a_multi_selects_selection_from_its_anchor() {
let mut list = multi_list(&FRUIT);
select_only(&mut list, 3);
assert!(move_caret_by(&mut list, 1, true, false));
assert_eq!(list.selected, BTreeSet::from([3, 4]));
assert!(move_caret_by(&mut list, 1, true, false));
assert_eq!(list.selected, BTreeSet::from([3, 4, 5]));
}
#[test]
fn a_bare_step_replaces_the_selection_and_re_anchors() {
let mut list = multi_list(&FRUIT);
select_only(&mut list, 3);
assert!(move_caret_by(&mut list, 1, false, false));
assert_eq!(list.selected, BTreeSet::from([4]));
assert!(move_caret_by(&mut list, 1, true, false));
assert_eq!(
list.selected,
BTreeSet::from([4, 5]),
"the bare step re-anchored on row 4"
);
}
#[test]
fn a_single_select_list_ignores_the_modifiers_entirely() {
for (shift, ctrl) in [(false, false), (true, false), (false, true), (true, true)] {
let mut list = single_list(&FRUIT);
select_only(&mut list, 3);
move_caret_by(&mut list, 1, shift, ctrl);
assert_eq!(
list.selected,
BTreeSet::from([4]),
"shift={shift} ctrl={ctrl} must still just select row 4"
);
}
}