use crate::{
Component,
RenderError,
Rendered,
theme::{
Palette,
Style,
Theme,
stylize,
},
};
pub struct Breadcrumbs {
items: Vec<String>,
separator: String,
}
impl Breadcrumbs {
pub fn new(items: Vec<impl Into<String>>) -> Self {
Self {
items: items.into_iter().map(Into::into).collect(),
separator: " > ".to_string(),
}
}
pub fn separator(mut self, sep: impl Into<String>) -> Self {
self.separator = sep.into();
self
}
}
impl Component for Breadcrumbs {
fn render(&self, _width: u16) -> Result<Rendered, RenderError> {
let theme = Theme::current();
let primary_style = Style::new().fg(theme.text_primary()).bold();
let secondary_style = Style::new().fg(theme.text_secondary());
let separator_style = Style::new().fg(theme.border_default());
let mut parts: Vec<String> = Vec::new();
let count = self.items.len();
for (i, item) in self.items.iter().enumerate() {
if i > 0 {
parts.push(stylize(&self.separator, &separator_style));
}
let styled = if i == count.saturating_sub(1) {
stylize(item, &primary_style)
} else {
stylize(item, &secondary_style)
};
parts.push(styled);
}
let line = parts.concat();
Ok(Rendered {
lines: vec![line],
cursor: None,
images: Vec::new(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::theme::Theme;
#[test]
fn renders_single_item_bold() {
Theme::with(Theme::Light, || {
let bc = Breadcrumbs::new(vec!["Home"]);
let rendered = bc.render(80).unwrap();
assert_eq!(rendered.lines.len(), 1);
let line = &rendered.lines[0];
assert!(line.contains("Home"));
assert!(line.contains("\x1b[1m"));
});
}
#[test]
fn renders_multiple_items_with_separator() {
Theme::with(Theme::Light, || {
let bc = Breadcrumbs::new(vec!["Home", "Settings", "Account"]);
let rendered = bc.render(80).unwrap();
let line = &rendered.lines[0];
assert!(line.contains("Home"));
assert!(line.contains("Settings"));
assert!(line.contains("Account"));
assert!(line.contains(" > "));
});
}
#[test]
fn custom_separator() {
Theme::with(Theme::Light, || {
let bc = Breadcrumbs::new(vec!["A", "B"]).separator(" / ");
let rendered = bc.render(80).unwrap();
let line = &rendered.lines[0];
assert!(line.contains(" / "));
assert!(!line.contains(" > "));
});
}
#[test]
fn empty_items_renders_empty_line() {
Theme::with(Theme::Light, || {
let bc = Breadcrumbs::new(Vec::<String>::new());
let rendered = bc.render(80).unwrap();
assert_eq!(rendered.lines.len(), 1);
assert_eq!(rendered.lines[0], "");
});
}
#[test]
fn uses_primary_color_for_last_item() {
Theme::with(Theme::Light, || {
let bc = Breadcrumbs::new(vec!["Home", "Settings"]);
let rendered = bc.render(80).unwrap();
let line = &rendered.lines[0];
assert!(line.contains("\x1b[38;2;31;31;31m"));
});
}
#[test]
fn uses_secondary_color_for_earlier_items() {
Theme::with(Theme::Light, || {
let bc = Breadcrumbs::new(vec!["Home", "Settings"]);
let rendered = bc.render(80).unwrap();
let line = &rendered.lines[0];
assert!(line.contains("\x1b[38;2;102;102;102m"));
});
}
#[test]
fn last_item_is_bold() {
Theme::with(Theme::Light, || {
let bc = Breadcrumbs::new(vec!["Home", "Settings"]);
let rendered = bc.render(80).unwrap();
let line = &rendered.lines[0];
assert!(line.contains("\x1b[1m"));
});
}
}