use super::*;
fn line_text(line: &Line<'static>) -> String {
line.spans.iter().map(|s| s.content.as_ref()).collect()
}
fn row(key: &str, options: &[(&str, bool)], selected: bool) -> String {
line_text(&cycle_row(Span::raw(" "), key, options, selected))
}
fn value_col(key: &str, rendered: &str) -> usize {
let after_key = rendered.find(key).expect("row renders its key") + key.chars().count();
after_key
+ rendered[after_key..]
.find(|c: char| c != ' ')
.expect("row renders a value")
}
fn toggles() -> ToggleState {
ToggleState {
wrap_off: false,
burn_aware: false,
preemptive: false,
refresh_spent: true,
}
}
#[test]
fn key_cell_is_uniform_width() {
for key in ["theme", "weekly limit", "on mismatch", "poll spent"] {
assert_eq!(
key_cell(key, KEY_W, KEY_GUTTER).chars().count(),
KEY_W + KEY_GUTTER,
"{key} key block must be exactly KEY_W + KEY_GUTTER wide"
);
}
}
#[test]
fn every_blurred_row_starts_its_value_at_the_shared_column() {
let value_col = 2 + KEY_W + KEY_GUTTER;
for r in GLOBAL_CONFIG_ROWS {
let line = line_text(&detail_row(r, false, toggles(), 60_000, 95.0, None, None));
let before: String = line.chars().take(value_col).collect();
assert!(
before.ends_with(&" ".repeat(KEY_GUTTER)),
"{r:?} key overruns KEY_W or drops the gutter: {before:?}"
);
assert_ne!(
line.chars().nth(value_col),
Some(' '),
"{r:?} value column must open on a label/glyph, not a space"
);
}
}
#[test]
fn longest_key_aligns_with_shortest() {
let theme = row("theme", &[("full", true), ("compatible", false)], false);
let weekly = row("weekly limit", &[("90%", true), ("95%", false)], false);
assert_eq!(value_col("theme", &theme), 2 + KEY_W + KEY_GUTTER);
assert_eq!(
value_col("theme", &theme),
value_col("weekly limit", &weekly),
"`weekly limit` (== KEY_W chars) must not push its value column right"
);
}
#[test]
fn active_first_option_aligns_with_inactive_first_option() {
let theme = row("theme", &[("full", true), ("compatible", false)], false);
let mismatch = row(
"on mismatch",
&[("ask", false), ("overwrite", true), ("new", false)],
false,
);
assert_eq!(
value_col("theme", &theme),
value_col("on mismatch", &mismatch),
);
}
#[test]
fn focus_wraps_the_active_option_in_brackets() {
let options = [("ask", false), ("overwrite", true), ("new", false)];
let blurred = row("on mismatch", &options, false);
let focused = row("on mismatch", &options, true);
assert!(!blurred.contains('['), "{blurred:?}");
assert!(focused.contains("[overwrite]"), "{focused:?}");
assert_eq!(
focused.chars().count(),
blurred.chars().count() + 2,
"the bracket pair is the only width change"
);
assert_eq!(
blurred.find("ask"),
focused.find("ask"),
"a label ahead of the active option holds its column"
);
}
#[test]
fn cycle_row_renders_the_contract_shape() {
let options = [("off", true), ("basic", false), ("strict", false)];
assert_eq!(
row("verify", &options, false),
" verify off basic strict",
);
assert_eq!(
row("verify", &options, true),
" verify [off] basic strict",
);
}
#[test]
fn edit_line_buffer_starts_at_the_value_column() {
let input = InputState {
value: "45".to_string(),
cursor: 2,
};
for rendered in [
line_text(&weekly_edit_line(Span::raw(" "), &input)),
line_text(&refresh_edit_line(Span::raw(" "), &input)),
] {
assert_eq!(
rendered.find("45"),
Some(2 + KEY_W + KEY_GUTTER),
"typed buffer must start at the shared value column: {rendered:?}"
);
}
}
#[test]
fn poll_spent_renders_as_a_toggle_not_a_cycle() {
let on = line_text(&detail_row(
GlobalConfigRow::RefreshSpentAccounts,
false,
toggles(),
60_000,
95.0,
None,
None,
));
assert!(on.contains(theme::toggle_on()), "on state glyph: {on}");
assert!(
!on.contains("off"),
"must not render the cycle off-option: {on}"
);
let mut off = toggles();
off.refresh_spent = false;
let off_line = line_text(&detail_row(
GlobalConfigRow::RefreshSpentAccounts,
false,
off,
60_000,
95.0,
None,
None,
));
assert!(
off_line.contains(theme::toggle_off()),
"off state glyph: {off_line}"
);
assert!(
!off_line.contains(" on"),
"must not render the cycle on-option: {off_line}"
);
}