#[test]
fn one_line_takes_the_error_then_the_note_then_the_hint() {
let style = PieceStyle::default();
let mut f = Field::new(FieldKind::Text, "title", "Title");
f.hint = Some("how it works");
assert_eq!(message_of(&style, &f).unwrap().0, "how it works");
f.note = Some((Tone::Warning, "what it costs"));
assert_eq!(message_of(&style, &f).unwrap().0, "what it costs");
assert_eq!(message_of(&style, &f).unwrap().1, style.warning);
f.error = Some("what is wrong");
assert_eq!(message_of(&style, &f).unwrap().0, "what is wrong");
assert_eq!(message_of(&style, &f).unwrap().1, style.danger);
f.error = None;
f.note = Some((Tone::Neutral, "an ordinary fact"));
assert_eq!(message_of(&style, &f).unwrap().1, style.content);
}
use super::*;
use makeover_layout::{Choice, State};
fn style() -> PieceStyle {
PieceStyle {
content: Style::new().add_modifier(Modifier::BOLD),
secondary: Style::new().add_modifier(Modifier::ITALIC),
muted: Style::new().add_modifier(Modifier::DIM),
danger: Style::new().add_modifier(Modifier::CROSSED_OUT),
..PieceStyle::default()
}
}
fn buffer(width: u16, height: u16) -> Buffer {
Buffer::empty(Rect::new(0, 0, width, height))
}
fn rows(buf: &Buffer) -> Vec<String> {
(0..buf.area.height)
.map(|y| {
(0..buf.area.width)
.map(|x| {
buf.cell((x, y))
.map_or(' ', |c| c.symbol().chars().next().unwrap_or(' '))
})
.collect::<String>()
.trim_end()
.to_owned()
})
.collect()
}
#[test]
fn a_bar_fills_in_proportion_and_reads_out_the_two_numbers() {
let style = style();
let line = meter(&style, &Meter::new(3, 10).label("subtasks"));
let drawn: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(drawn, "###------- 3/10 subtasks");
let bare = meter(&style, &Meter::new(3, 10));
let drawn: String = bare.spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(drawn, "###------- 3/10");
}
#[test]
fn an_empty_set_is_an_empty_bar_rather_than_a_divide_by_zero() {
let line = meter(&style(), &Meter::new(0, 0));
let drawn: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(drawn, "---------- 0/0");
}
#[test]
fn an_over_run_fills_the_bar_and_still_reports_the_overflow() {
let line = meter(&style(), &Meter::new(14, 10));
let drawn: String = line.spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(drawn, "########## 14/10");
}
#[test]
fn a_badge_is_round_and_a_chip_is_square() {
let style = style();
let badge = token(&style, "draft", Token::Badge, Tone::Neutral, false, false);
assert_eq!(badge.to_string(), "(draft)");
let chip = token(
&style,
"rust",
Token::Chip { removable: false },
Tone::Neutral,
false,
false,
);
assert_eq!(chip.to_string(), "[rust]");
}
#[test]
fn a_filled_badge_is_an_edge_its_label_and_an_edge_in_the_same_two_cells() {
use ratatui::style::Color;
let danger = BadgeStyle {
fill: Style::new().fg(Color::White).bg(Color::Red),
edge: Style::new().fg(Color::LightRed),
};
let mut badges = [BadgeStyle::default(); 5];
badges[4] = danger;
let style = PieceStyle {
badges,
badge_edges: Some(["<", ">"]),
..style()
};
let badge = token(&style, "failed", Token::Badge, Tone::Danger, false, false);
assert_eq!(badge.to_string(), "<failed>");
assert_eq!(badge.width(), "(failed)".len());
assert_eq!(badge.spans[1].style, danger.fill);
assert_eq!(badge.spans[0].style.fg, Some(Color::LightRed));
assert_eq!(badge.spans[0].style.bg, Some(Color::Red));
assert_eq!(badge.spans[2].style, badge.spans[0].style);
let chip = token(
&style,
"rust",
Token::Chip { removable: true },
Tone::Danger,
false,
false,
);
assert_eq!(chip.to_string(), "[rust]");
}
#[test]
fn a_latched_chip_reads_the_same_as_a_focused_one() {
let style = style();
let kind = Token::Chip { removable: false };
let latched = token(&style, "rust", kind, Tone::Neutral, true, false);
let focused = token(&style, "rust", kind, Tone::Neutral, false, true);
assert_eq!(latched.spans[0].style, focused.spans[0].style);
assert!(
latched.spans[0]
.style
.add_modifier
.contains(Modifier::REVERSED)
);
}
#[test]
fn a_control_draws_its_key_only_where_one_was_named() {
let style = style();
let line = act(&style, &Act::new("Delete"), false);
assert_eq!(line.spans[0].content.as_ref(), "< Delete >");
let line = act(&style, &Act::new("Quit").key("q"), false);
assert_eq!(line.spans[0].content.as_ref(), "< Quit > (q)");
}
#[test]
fn the_control_that_commits_is_bracketed_and_the_leading_one_is_filled() {
let style = style();
let save = act(&style, &Act::new("Save").committing(), false);
assert_eq!(save.spans[0].content.as_ref(), "[ Save ]");
assert_ne!(save.spans[0].style, style.filled);
let reply = act(&style, &Act::new("Reply").leading(), false);
assert_eq!(reply.spans[0].content.as_ref(), "< Reply >");
assert_eq!(reply.spans[0].style, style.filled);
let queue = act(&style, &Act::new("Queue").committing().leading(), false);
assert_eq!(queue.spans[0].content.as_ref(), "[ Queue ]");
assert_eq!(queue.spans[0].style, style.filled);
let delete = act(
&style,
&Act::new("Delete").tone(Tone::Danger).committing().leading(),
false,
);
assert_eq!(delete.spans[0].content.as_ref(), "[ Delete ]");
assert_eq!(delete.spans[0].style, style.danger);
}
#[test]
fn a_disabled_control_is_never_marked_focused() {
let style = style();
let disabled = Act::new("Save").state(State::Disabled);
let line = act(&style, &disabled, true);
assert!(
!line.spans[0]
.style
.add_modifier
.contains(Modifier::REVERSED)
);
assert_eq!(line.spans[0].style, style.muted);
let unstated = Act::new("Save");
let line = act(&style, &unstated, true);
assert!(
line.spans[0]
.style
.add_modifier
.contains(Modifier::REVERSED)
);
}
#[test]
fn a_danger_control_keeps_its_tone_under_focus() {
let style = style();
let line = act(&style, &Act::new("Delete").tone(Tone::Danger), true);
assert_eq!(
line.spans[0].style.add_modifier,
style.danger.add_modifier | Modifier::REVERSED
);
}
#[test]
fn facts_put_every_value_in_one_column() {
let style = style();
let facts_ = [
Fact::new("Due", "2d ago"),
Fact::new("Project", "Kitchen"),
Fact::new("Where", "12 Rose St"),
];
let mut buf = buffer(30, 4);
let used = facts(&style, &facts_, buf.area, &mut buf);
assert_eq!(used, 3);
let drawn = rows(&buf);
assert_eq!(drawn[0], "Due 2d ago".to_owned());
assert_eq!(drawn[1], "Project Kitchen".to_owned());
assert_eq!(drawn[2], "Where 12 Rose St".to_owned());
assert_eq!(facts_height(&facts_, 30), 3);
}
#[test]
fn a_facts_label_column_never_takes_more_than_half() {
let style = style();
let facts_ = [Fact::new("An extremely long label indeed", "yes")];
let mut buf = buffer(20, 2);
facts(&style, &facts_, buf.area, &mut buf);
let drawn = rows(&buf);
assert!(drawn[0].starts_with("An extrem"), "{drawn:?}");
assert!(drawn[0].trim_end().ends_with("yes"), "{drawn:?}");
}
#[test]
fn a_figure_puts_the_number_over_what_it_counts() {
let style = style();
let figure_ = Figure::new("42", "open tasks");
let mut buf = buffer(20, 4);
let used = figure(&style, &figure_, buf.area, &mut buf);
assert_eq!(used, 2);
assert_eq!(rows(&buf)[..2], ["42".to_owned(), "open tasks".to_owned()]);
assert_eq!(figure_height(&figure_, 20), 2);
}
#[test]
fn a_figures_change_rides_on_the_value_row() {
let style = style();
let figure_ = Figure::new("42", "open tasks")
.change("+3")
.tone(Tone::Success);
let mut buf = buffer(20, 4);
figure(&style, &figure_, buf.area, &mut buf);
assert_eq!(rows(&buf)[0], "42 +3");
}
#[test]
fn a_compulsory_field_says_so_in_its_label() {
let style = style();
let mut field_ = Field::new(FieldKind::Text, "email", "Email");
field_.required = true;
let mut buf = buffer(20, 4);
field(&style, &field_, Held::Absent, false, buf.area, &mut buf);
assert_eq!(rows(&buf)[0], "Email *");
}
#[test]
fn a_hidden_field_costs_no_rows_at_all() {
let style = style();
let field_ = Field::new(FieldKind::Hidden, "csrf", "Token");
let mut buf = buffer(20, 4);
assert_eq!(
field(
&style,
&field_,
Held::Text("abc"),
false,
buf.area,
&mut buf
),
0
);
assert_eq!(field_height(&style, &field_, 20), 0);
assert_eq!(rows(&buf)[0], "");
}
#[test]
fn a_secret_is_dotted_from_the_callers_buffer_and_never_from_the_description() {
let style = style();
let field_ = Field::new(FieldKind::Secret, "password", "Password");
let mut buf = buffer(20, 4);
field(
&style,
&field_,
Held::Text("hunter2"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1], "*******");
}
#[test]
fn an_error_takes_the_row_the_hint_would_have_had() {
let style = style();
let mut field_ = Field::new(FieldKind::Text, "email", "Email");
field_.hint = Some("work address");
field_.error = Some("not an address");
let mut buf = buffer(20, 5);
field(
&style,
&field_,
Held::Text("nope"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[2], "not an address");
assert_eq!(field_height(&style, &field_, 20), 3);
}
#[test]
fn a_focused_empty_box_shows_where_the_typing_will_land() {
let style = style();
let field_ = Field::new(FieldKind::Text, "email", "Email");
let mut buf = buffer(20, 4);
field(&style, &field_, Held::Absent, true, buf.area, &mut buf);
let caret = buf.cell((0, 1)).expect("the well's first cell").style();
assert!(caret.add_modifier.contains(Modifier::REVERSED));
}
#[test]
fn a_choice_field_marks_the_chosen_option_and_costs_a_row_each() {
let style = style();
let mut field_ = Field::new(FieldKind::Radio, "size", "Size");
let options = [Choice::plain("small"), Choice::plain("large")];
field_.options = &options;
let mut buf = buffer(20, 5);
field(
&style,
&field_,
Held::Text("large"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1], "( ) small");
assert_eq!(rows(&buf)[2], "(*) large");
assert_eq!(field_height(&style, &field_, 20), 3);
}
#[test]
fn a_checklist_ticks_its_marked_options_and_ignores_the_held_text() {
let style = style();
let options = [
Choice::new("blog", "Blog").chosen(),
Choice::new("forum", "Forum"),
Choice::new("git", "Git").chosen(),
];
let field_ = Field::checklist("feature", "Features", &options);
let mut buf = buffer(20, 5);
field(
&style,
&field_,
Held::Text("forum"),
false,
buf.area,
&mut buf,
);
let drawn = rows(&buf);
assert_eq!(drawn[1].trim_end(), "[x] Blog");
assert_eq!(drawn[2].trim_end(), "[ ] Forum");
assert_eq!(drawn[3].trim_end(), "[x] Git");
assert_eq!(field_height(&style, &field_, 20), 4);
}
#[test]
fn the_option_under_the_caret_takes_the_focus_mark_and_a_chosen_one_does_not() {
let style = style();
assert!(!style.focus.is_empty(), "a focus mark to look for");
let options = [
Choice::new("blog", "Blog").chosen(),
Choice::new("forum", "Forum"),
];
let field_ = Field::checklist("feature", "Features", &options);
let mut buf = buffer(20, 4);
field_at(
&style,
&field_,
Held::Absent,
true,
Some(1),
buf.area,
&mut buf,
);
assert!(
buf[(0, 2)].modifier.contains(style.focus),
"Forum is walked"
);
assert!(
!buf[(0, 1)].modifier.contains(style.focus),
"Blog is ticked and not walked"
);
let mut still = buffer(20, 4);
field_at(
&style,
&field_,
Held::Absent,
false,
Some(1),
still.area,
&mut still,
);
assert!(!still[(0, 2)].modifier.contains(style.focus));
}
#[test]
fn a_range_draws_its_two_ends_and_where_the_value_sits_between_them() {
let style = style();
let field_ = Field::range("review", "Review above", "0", "1");
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Text("0.5"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1].trim_end(), "0 #####----- 1 0.5");
assert_eq!(field_height(&style, &field_, 40), 2);
}
#[test]
fn a_unit_rides_on_the_value_and_not_on_the_label() {
let style = style();
let field_ = Field {
unit: Some("s"),
..Field::range("attack", "Attack", "0", "5")
};
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Text("2.5"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[0].trim_end(), "Attack");
assert_eq!(rows(&buf)[1].trim_end(), "0 #####----- 5 2.5 s");
}
#[test]
fn a_typed_number_reads_with_its_unit_too() {
let style = style();
let field_ = Field {
unit: Some("ms"),
..Field::new(FieldKind::Number, "fade", "Fade")
};
let mut buf = buffer(40, 3);
field(&style, &field_, Held::Text("50"), false, buf.area, &mut buf);
assert_eq!(rows(&buf)[1].trim_end(), "50 ms");
}
#[test]
fn a_unit_on_a_kind_that_is_not_a_quantity_is_ignored() {
let style = style();
let field_ = Field {
unit: Some("s"),
..Field::new(FieldKind::Text, "name", "Name")
};
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Text("kick"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1].trim_end(), "kick");
}
#[test]
fn an_interval_is_one_line_with_both_ends_on_it() {
let style = style();
let field_ = Field {
min: Some("0"),
max: Some("300"),
unit: Some("BPM"),
..Field::interval("bpm_min", "bpm_max", "BPM range")
};
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Between {
lower: "90",
upper: "130",
},
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[0].trim_end(), "BPM range");
assert_eq!(rows(&buf)[1].trim_end(), "90 BPM to 130 BPM");
assert_eq!(rows(&buf)[2].trim_end(), "");
}
#[test]
fn an_open_end_falls_back_to_the_bound_it_means() {
let style = style();
let field_ = Field {
min: Some("0"),
max: Some("300"),
..Field::interval("bpm_min", "bpm_max", "BPM range")
};
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Between {
lower: "120",
upper: "",
},
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1].trim_end(), "120 to 300");
}
#[test]
fn an_unbounded_open_end_draws_nothing_rather_than_a_number() {
let style = style();
let field_ = Field::interval("bpm_min", "bpm_max", "BPM range");
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Between {
lower: "",
upper: "130",
},
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1].trim_end(), "to 130");
}
#[test]
fn a_range_holding_something_unreadable_still_shows_it() {
let style = style();
let field_ = Field::range("review", "Review above", "0", "1");
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Text("unset"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1].trim_end(), "0 ---------- 1 unset");
}
#[test]
fn an_unbounded_range_is_typed_into_rather_than_dragged() {
let style = style();
let field_ = Field {
max: Some("1"),
..Field::new(FieldKind::Range, "review", "Review above")
};
let mut buf = buffer(40, 3);
field(
&style,
&field_,
Held::Text("0.5"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1].trim_end(), "0.5");
}
#[test]
fn an_unavailable_option_reads_as_inert_and_says_why() {
let style = style();
let options = [
Choice::new("chromatic", "Chromatic"),
Choice::new("multi", "Multi-sample").unless("Drop a second sample."),
];
let mut field_ = Field::new(FieldKind::Radio, "mode", "Mode");
field_.options = &options;
let mut buf = buffer(46, 4);
field(
&style,
&field_,
Held::Text("chromatic"),
false,
buf.area,
&mut buf,
);
assert_eq!(rows(&buf)[1].trim_end(), "(*) Chromatic");
assert_eq!(
rows(&buf)[2].trim_end(),
"( ) Multi-sample: Drop a second sample."
);
let muted = buf.cell((0, 2)).expect("the unavailable row").style();
assert!(muted.add_modifier.contains(Modifier::DIM));
}
#[test]
fn an_option_can_carry_the_line_that_says_what_it_means() {
let style = style();
let options = [
Choice::new("16", "Basic").detailing("$16/mo. Fits text, blogs, newsletters."),
Choice::new("24", "Small Files"),
];
let mut field_ = Field::new(FieldKind::Radio, "tier", "Tier");
field_.options = &options;
let mut buf = buffer(46, 5);
field(&style, &field_, Held::Text("16"), false, buf.area, &mut buf);
let drawn = rows(&buf);
assert_eq!(drawn[1].trim_end(), "(*) Basic");
assert_eq!(
drawn[2].trim_end(),
" $16/mo. Fits text, blogs, newsletters."
);
assert_eq!(drawn[3].trim_end(), "( ) Small Files");
let muted = buf.cell((4, 2)).expect("the detail row").style();
assert!(muted.add_modifier.contains(Modifier::DIM));
}
#[test]
fn an_unchosen_option_does_not_read_as_disabled() {
let style = style();
let mut field_ = Field::new(FieldKind::Radio, "size", "Size");
let options = [Choice::plain("small"), Choice::plain("large")];
field_.options = &options;
let mut buf = buffer(20, 5);
field(
&style,
&field_,
Held::Text("large"),
false,
buf.area,
&mut buf,
);
let unchosen = buf.cell((0, 1)).expect("the first option").style();
assert_eq!(unchosen.add_modifier, style.secondary.add_modifier);
assert_ne!(unchosen.add_modifier, style.muted.add_modifier);
}
#[test]
fn a_checkbox_reads_a_bool_rather_than_a_submitted_string() {
let style = style();
let field_ = Field::new(FieldKind::Checkbox, "agree", "Agree");
let mut buf = buffer(20, 4);
field(&style, &field_, Held::On(true), false, buf.area, &mut buf);
assert_eq!(rows(&buf)[1], "[x]");
let mut buf = buffer(20, 4);
field(&style, &field_, Held::On(false), false, buf.area, &mut buf);
assert_eq!(rows(&buf)[1], "[ ]");
}
#[test]
fn a_markdown_field_gets_the_rows_a_textarea_does() {
let style = PieceStyle::default();
let rich = Field::new(FieldKind::Rich, "body", "Body");
let textarea = Field::new(FieldKind::Textarea, "body", "Body");
let plain = Field::new(FieldKind::Text, "body", "Body");
assert_eq!(
field_height(&style, &rich, 40),
field_height(&style, &textarea, 40)
);
assert!(field_height(&style, &rich, 40) > field_height(&style, &plain, 40));
}
#[test]
fn a_tone_and_a_heading_map_without_a_fallback_arm() {
let style = style();
assert_eq!(style.tone(Tone::Neutral), style.content);
assert_eq!(style.tone(Tone::Danger), style.danger);
assert_eq!(style.heading(Heading::Page), style.page);
assert_eq!(style.heading(Heading::Subsection), style.subsection);
}
#[test]
fn the_default_style_carries_no_colour_at_all() {
let style = PieceStyle::default();
for painted in [style.content, style.danger, style.page, style.action] {
assert_eq!(painted.fg, None);
assert_eq!(painted.bg, None);
}
}
#[test]
fn the_three_states_of_a_wait_are_three_drawings() {
let style = PieceStyle::default();
let bare = awaiting(&style, Awaiting::unmeasured(), Progress::default(), true);
let sized = awaiting(&style, Awaiting::of(41_943_040), Progress::default(), true);
let watched = awaiting(
&style,
Awaiting::of(40),
Progress {
delivered: Some(20),
elapsed: Some(Duration::from_secs(4)),
},
true,
);
let read = |line: &Line<'_>| {
line.spans
.iter()
.map(|s| s.content.to_string())
.collect::<String>()
};
assert_eq!(read(&bare), "#");
assert_eq!(read(&sized), "# 41943040");
assert_eq!(read(&watched), "#####----- 20/40 4s");
}
#[test]
fn a_dark_mark_still_occupies_its_cell() {
let style = PieceStyle::default();
assert_eq!(activity(&style, true).content.chars().count(), 1);
assert_eq!(activity(&style, false).content.chars().count(), 1);
}
#[test]
fn an_over_delivered_wait_clamps_and_does_not_panic() {
let style = PieceStyle::default();
let over = awaiting(
&style,
Awaiting::of(4),
Progress {
delivered: Some(9),
elapsed: None,
},
true,
);
assert!(over.spans[0].content.chars().all(|c| c == '#'));
assert_eq!(over.spans[0].content.chars().count(), 10);
let empty = awaiting(
&style,
Awaiting::of(0),
Progress {
delivered: Some(9),
elapsed: None,
},
true,
);
assert!(empty.spans[0].content.starts_with('-'));
}
#[test]
fn a_theme_picker_heads_each_group_and_marks_each_tier() {
const THEMES: &[makeover_layout::ThemeChoice<'_>] = &[
makeover_layout::ThemeChoice::new(
"goingson",
"GoingsOn",
ThemeVariant::Light,
makeover_layout::Contrast::High,
),
makeover_layout::ThemeChoice::new(
"carbonfox",
"Carbonfox",
ThemeVariant::Dark,
makeover_layout::Contrast::Standard,
),
];
let style = style();
let field_ = Field::theme("theme", "Theme", THEMES)
.following(makeover_layout::Choice::new("system", "Follow System"));
let mut buf = buffer(32, 8);
field(
&style,
&field_,
Held::Text("carbonfox"),
false,
buf.area,
&mut buf,
);
let rows = rows(&buf);
assert_eq!(rows[1], "( ) Follow System");
assert_eq!(rows[2], "Light");
assert_eq!(rows[3], "( ) GoingsOn [AA]");
assert_eq!(rows[4], "Dark");
assert_eq!(rows[5], "(*) Carbonfox [OK]");
}
#[test]
fn a_theme_picker_asks_for_the_rows_it_draws() {
const THEMES: &[makeover_layout::ThemeChoice<'_>] = &[
makeover_layout::ThemeChoice::new(
"goingson",
"GoingsOn",
ThemeVariant::Light,
makeover_layout::Contrast::High,
),
makeover_layout::ThemeChoice::new(
"carbonfox",
"Carbonfox",
ThemeVariant::Dark,
makeover_layout::Contrast::Standard,
),
];
let style = style();
let field_ = Field::theme("theme", "Theme", THEMES)
.following(makeover_layout::Choice::new("system", "Follow System"));
assert_eq!(field_height(&style, &field_, 32), 6);
let one = Field::theme("theme", "Theme", &THEMES[..1]);
assert_eq!(field_height(&style, &one, 32), 3);
}
#[test]
fn every_bar_in_a_chart_is_drawn_against_the_one_axis() {
let style = style();
let bars = [
Bar::at("Mar 3").of(10).reading("$0.10"),
Bar::at("Mar 4").of(5),
Bar::at("Mar 5").of(20).reading("$0.20").note("3 sales"),
];
let lines = chart(&style, &Chart::new(20), &bars);
let drawn: Vec<String> = lines
.iter()
.map(|line| {
line.spans
.iter()
.map(|s| s.content.as_ref())
.collect::<String>()
})
.collect();
assert_eq!(
drawn,
vec![
"Mar 3 #####----- $0.10",
"Mar 4 ###-------",
"Mar 5 ########## $0.20 / 3 sales",
]
);
}
#[test]
fn a_chart_over_nothing_draws_empty_bars() {
let lines = chart(&style(), &Chart::new(0), &[Bar::at("Mar 3").of(0)]);
let drawn: String = lines[0].spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(drawn, "Mar 3 ----------");
}
#[test]
fn the_places_are_padded_to_the_widest() {
let bars = [Bar::at("Mar 3").of(1), Bar::at("September").of(1)];
let lines = chart(&style(), &Chart::new(1), &bars);
let first: String = lines[0].spans.iter().map(|s| s.content.as_ref()).collect();
let second: String = lines[1].spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(first.find('#'), second.find('#'));
}