[WIP] Daemon & CLI
This commit is contained in:
parent
56aad3a023
commit
8b158108bb
100 changed files with 6519 additions and 1596 deletions
15
iota-cli/tests/button_layout.rs
Normal file
15
iota-cli/tests/button_layout.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use iota_cli::controls::button::{button_minimum_width, horizontal_button_widths};
|
||||
|
||||
#[test]
|
||||
fn width_allocation_handles_exact_spare_and_insufficient_space() {
|
||||
assert_eq!(horizontal_button_widths(7, &[3, 4]), Some(vec![3, 4]));
|
||||
assert_eq!(horizontal_button_widths(10, &[3, 4]), Some(vec![5, 5]));
|
||||
assert_eq!(horizontal_button_widths(6, &[3, 4]), None);
|
||||
assert_eq!(horizontal_button_widths(10, &[]), Some(Vec::new()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minimum_width_uses_terminal_columns() {
|
||||
assert_eq!(button_minimum_width("é"), 3);
|
||||
assert_eq!(button_minimum_width("界"), 4);
|
||||
}
|
||||
71
iota-cli/tests/choice_rendering.rs
Normal file
71
iota-cli/tests/choice_rendering.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use iota_cli::{
|
||||
controls::choice::{ChoiceKind, ChoiceVisualState, render_choice_line},
|
||||
theme::{ThemeName, resolve},
|
||||
};
|
||||
use ratatui::style::{Color, Modifier};
|
||||
|
||||
#[test]
|
||||
fn ansi_checkbox_matches_the_existing_focused_and_disabled_styles() {
|
||||
let theme = resolve(ThemeName::Ansi);
|
||||
let line = render_choice_line(
|
||||
"Terms",
|
||||
ChoiceKind::Checkbox,
|
||||
ChoiceVisualState {
|
||||
selected: false,
|
||||
focused: true,
|
||||
enabled: true,
|
||||
},
|
||||
&theme,
|
||||
);
|
||||
assert_eq!(
|
||||
line.spans
|
||||
.iter()
|
||||
.map(|span| span.content.as_ref())
|
||||
.collect::<String>(),
|
||||
"[ ] Terms"
|
||||
);
|
||||
assert_eq!(line.spans[1].style.fg, Some(Color::Yellow));
|
||||
assert!(line.spans[1].style.add_modifier.contains(Modifier::BOLD));
|
||||
|
||||
let disabled = render_choice_line(
|
||||
"Terms",
|
||||
ChoiceKind::Checkbox,
|
||||
ChoiceVisualState {
|
||||
selected: false,
|
||||
focused: true,
|
||||
enabled: false,
|
||||
},
|
||||
&theme,
|
||||
);
|
||||
assert_eq!(disabled.spans[1].style.fg, Some(Color::DarkGray));
|
||||
assert_eq!(disabled.spans[3].style.fg, Some(Color::Red));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colourless_themes_keep_state_and_focus_visible() {
|
||||
for name in [ThemeName::Monospace, ThemeName::Binary] {
|
||||
let theme = resolve(name);
|
||||
let line = render_choice_line(
|
||||
"Mode",
|
||||
ChoiceKind::Radio,
|
||||
ChoiceVisualState {
|
||||
selected: true,
|
||||
focused: true,
|
||||
enabled: true,
|
||||
},
|
||||
&theme,
|
||||
);
|
||||
assert_eq!(
|
||||
line.spans
|
||||
.iter()
|
||||
.map(|span| span.content.as_ref())
|
||||
.collect::<String>(),
|
||||
"> (x) Mode <"
|
||||
);
|
||||
assert!(
|
||||
line.spans
|
||||
.iter()
|
||||
.all(|span| span.style.fg.is_none() && span.style.bg.is_none())
|
||||
);
|
||||
}
|
||||
}
|
||||
109
iota-cli/tests/control_state.rs
Normal file
109
iota-cli/tests/control_state.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use iota_cli::controls::{
|
||||
checkbox_group::{CheckboxChange, CheckboxGroup, CheckboxItem},
|
||||
navigation::DisabledFocusPolicy,
|
||||
radio_group::{DisabledSelectionPolicy, RadioChange, RadioGroup, RadioGroupError, RadioItem},
|
||||
};
|
||||
|
||||
fn checkbox(value: u8, enabled: bool) -> CheckboxItem<u8> {
|
||||
CheckboxItem {
|
||||
value,
|
||||
label: value.to_string(),
|
||||
description: None,
|
||||
enabled,
|
||||
disabled_reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn radio(value: u8, enabled: bool) -> RadioItem<u8> {
|
||||
RadioItem {
|
||||
value,
|
||||
label: value.to_string(),
|
||||
description: None,
|
||||
enabled,
|
||||
disabled_reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkbox_selection_and_disabled_focus_are_independent() {
|
||||
let mut group = CheckboxGroup::new(
|
||||
vec![checkbox(1, true), checkbox(2, false), checkbox(3, true)],
|
||||
[1, 99],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
group.selected().iter().copied().collect::<Vec<_>>(),
|
||||
vec![1]
|
||||
);
|
||||
assert_eq!(group.toggle_focused(), CheckboxChange::Deselected(1));
|
||||
group.focus_next();
|
||||
assert_eq!(group.focused_item().unwrap().value, 3);
|
||||
group.set_focus_policy(DisabledFocusPolicy::Include);
|
||||
group.focus_previous();
|
||||
assert_eq!(group.focused_item().unwrap().value, 2);
|
||||
assert_eq!(group.toggle_focused(), CheckboxChange::IgnoredDisabled(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkbox_non_wrapping_navigation_stops_at_the_edge() {
|
||||
let mut group = CheckboxGroup::new(vec![checkbox(1, true), checkbox(2, true)], []).unwrap();
|
||||
group.set_wrap_navigation(false);
|
||||
group.focus_previous();
|
||||
assert_eq!(group.focused_item().unwrap().value, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn radio_validates_default_and_preserves_one_selection() {
|
||||
assert!(matches!(
|
||||
RadioGroup::new(Vec::<RadioItem<u8>>::new(), None, 1),
|
||||
Err(RadioGroupError::Empty)
|
||||
));
|
||||
assert!(matches!(
|
||||
RadioGroup::new(vec![radio(1, true)], None, 2),
|
||||
Err(RadioGroupError::DefaultMissing)
|
||||
));
|
||||
assert!(matches!(
|
||||
RadioGroup::new(vec![radio(1, false)], None, 1),
|
||||
Err(RadioGroupError::DefaultDisabled)
|
||||
));
|
||||
|
||||
let mut group = RadioGroup::new(vec![radio(1, true), radio(2, true)], Some(2), 1).unwrap();
|
||||
assert_eq!(group.selected(), &2);
|
||||
group.focus_next();
|
||||
assert_eq!(group.selected(), &2);
|
||||
assert_eq!(group.select_focused(), RadioChange::Unchanged(2));
|
||||
group.focus_previous();
|
||||
assert_eq!(
|
||||
group.select_focused(),
|
||||
RadioChange::Changed {
|
||||
previous: 2,
|
||||
selected: 1
|
||||
}
|
||||
);
|
||||
assert_eq!(group.selected(), &1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groups_initially_focus_the_first_enabled_item() {
|
||||
let checkboxes = CheckboxGroup::new(vec![checkbox(1, false), checkbox(2, true)], []).unwrap();
|
||||
assert_eq!(checkboxes.focused_item().unwrap().value, 2);
|
||||
let radios = RadioGroup::new(vec![radio(1, false), radio(2, true)], None, 2).unwrap();
|
||||
assert_eq!(radios.focused_item().value, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabling_a_selected_radio_obeys_the_configured_policy() {
|
||||
let mut group = RadioGroup::new(vec![radio(1, true), radio(2, true)], Some(2), 1).unwrap();
|
||||
group.set_enabled(&2, false).unwrap();
|
||||
assert_eq!(group.selected(), &1);
|
||||
|
||||
group.set_enabled(&2, true).unwrap();
|
||||
group.focus_next();
|
||||
group.select_focused();
|
||||
group.set_disabled_selection_policy(DisabledSelectionPolicy::ReturnError);
|
||||
assert_eq!(
|
||||
group.set_enabled(&2, false),
|
||||
Err(RadioGroupError::SelectedItemDisabled)
|
||||
);
|
||||
assert_eq!(group.selected(), &2);
|
||||
}
|
||||
45
iota-cli/tests/layout_fit.rs
Normal file
45
iota-cli/tests/layout_fit.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use iota_cli::layout::fit::{
|
||||
FitLevel, RequiredSize, centered_rect, inset_checked, reserve_vertical, select_fit_level,
|
||||
};
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
#[test]
|
||||
fn selects_fit_by_both_dimensions() {
|
||||
let preferred = RequiredSize {
|
||||
width: 80,
|
||||
height: 20,
|
||||
};
|
||||
let compact = RequiredSize {
|
||||
width: 50,
|
||||
height: 12,
|
||||
};
|
||||
assert_eq!(
|
||||
select_fit_level(Rect::new(0, 0, 80, 20), preferred, compact),
|
||||
FitLevel::Preferred
|
||||
);
|
||||
assert_eq!(
|
||||
select_fit_level(Rect::new(0, 0, 50, 12), preferred, compact),
|
||||
FitLevel::Compact
|
||||
);
|
||||
assert_eq!(
|
||||
select_fit_level(Rect::new(0, 0, 80, 11), preferred, compact),
|
||||
FitLevel::Fallback
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rectangle_helpers_do_not_underflow() {
|
||||
let zero = Rect::new(4, 5, 0, 0);
|
||||
assert_eq!(
|
||||
centered_rect(
|
||||
zero,
|
||||
RequiredSize {
|
||||
width: 10,
|
||||
height: 10
|
||||
}
|
||||
),
|
||||
zero
|
||||
);
|
||||
assert_eq!(reserve_vertical(zero, 1, 0), None);
|
||||
assert_eq!(inset_checked(zero, 1, 1), None);
|
||||
}
|
||||
Loading…
Reference in a new issue