hefesto-widgets 0.7.3

Ratatui widgets for the Hefesto TUI toolkit: popups, scrollable lists, trees, text input and spinners
Documentation
use super::*;
use insta::assert_snapshot;
use ratatui::{
    backend::TestBackend,
    style::Color,
    Terminal,
};

// ── Helpers ──────────────────────────────────────────

fn render(name: &str, stack: BadgeStack<'_>, state: &mut BadgeStackState) {
    let backend = TestBackend::new(60, 10);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal
        .draw(|f| f.render_stateful_widget(stack, f.area(), state))
        .unwrap();
    assert_snapshot!(name, terminal.backend());
}

// ── Unit: Badge constructors ─────────────────────────

#[test]
fn badge_new_defaults() {
    let badge = Badge::new(" hello", Color::Green);
    assert_eq!(badge.anchor, BadgeAnchor::TopLeft);
    assert!(badge.closable);
}

#[test]
fn badge_with_content_custom() {
    let badge = Badge::with_content(
        vec![Line::from("custom")],
        Color::Blue,
    );
    assert_eq!(badge.content[0], Line::from("custom"));
    assert_eq!(badge.border_color, Color::Blue);
}

// ── Unit: Badge builders ────────────────────────────

#[test]
fn badge_builder_anchor() {
    let badge = Badge::new("x", Color::Red).anchor(BadgeAnchor::BottomRight);
    assert_eq!(badge.anchor, BadgeAnchor::BottomRight);
}

#[test]
fn badge_builder_closable() {
    let badge = Badge::new("x", Color::Red).closable(false);
    assert!(!badge.closable);
}

#[test]
fn badge_builder_title() {
    let badge = Badge::new("content", Color::Red).title("my title");
    assert_eq!(badge.title, Some("my title"));
}

#[test]
fn badge_builder_border_type() {
    let badge = Badge::new("x", Color::Red).border_type(BorderType::Double);
    assert_eq!(badge.border_type, BorderType::Double);
}

#[test]
fn badge_builder_padding() {
    let badge = Badge::new("x", Color::Red).padding(2);
    assert_eq!(badge.padding, 2);
}

#[test]
fn badge_builder_bg_color() {
    let badge = Badge::new("x", Color::Red).bg_color(Color::Black);
    assert_eq!(badge.bg_color, Some(Color::Black));
}

#[test]
fn badge_builder_z_index() {
    let badge = Badge::new("x", Color::Red).z_index(5);
    assert_eq!(badge.z_index, 5);
}

#[test]
fn badge_default_layer_is_zero() {
    let badge = Badge::new("x", Color::Red);
    assert_eq!(badge.layer, 0);
}

#[test]
fn badge_builder_layer() {
    let badge = Badge::new("x", Color::Red).layer(3);
    assert_eq!(badge.layer, 3);
}

#[test]
fn badge_default_z_index_is_five() {
    let badge = Badge::new("x", Color::Red);
    assert_eq!(badge.z_index, 5);
}

// ── Unit: BadgeStack ────────────────────────────────

#[test]
fn stack_new_is_empty() {
    let stack = BadgeStack::new();
    assert_eq!(stack.badges.len(), 0);
}

#[test]
fn stack_push_accumulates() {
    let stack = BadgeStack::new()
        .push(Badge::new("a", Color::Red))
        .push(Badge::new("b", Color::Green));
    assert_eq!(stack.badges.len(), 2);
}

// ── Unit: BadgeStackState ────────────────────────────

#[test]
fn state_default_all_visible() {
    let state = BadgeStackState::default();
    assert!(state.is_visible(0));
    assert!(state.is_visible(9));
}

#[test]
fn state_dismiss_marks_invisible() {
    let mut state = BadgeStackState::default();
    state.dismiss(2);
    assert!(!state.is_visible(2));
    assert!(state.is_visible(0));
}

#[test]
fn state_reset_restores_all() {
    let mut state = BadgeStackState::default();
    state.dismiss(0);
    state.dismiss(3);
    state.reset();
    assert!(state.is_visible(0));
    assert!(state.is_visible(3));
}

// ── Snapshots ───────────────────────────────────────

#[test]
fn snapshot_top_left() {
    let stack = BadgeStack::new()
        .push(Badge::new(" main", Color::Green).anchor(BadgeAnchor::TopLeft));
    render("top_left", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_top_right() {
    let stack = BadgeStack::new()
        .push(Badge::new(" 3 warnings", Color::Yellow).anchor(BadgeAnchor::TopRight));
    render("top_right", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_bottom_left() {
    let stack = BadgeStack::new()
        .push(Badge::new(" saved", Color::Cyan).anchor(BadgeAnchor::BottomLeft));
    render("bottom_left", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_bottom_right() {
    let stack = BadgeStack::new()
        .push(Badge::new(" build: ok", Color::Blue).anchor(BadgeAnchor::BottomRight));
    render("bottom_right", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_stack_top_left_multi() {
    let stack = BadgeStack::new()
        .push(Badge::new(" a", Color::Green).anchor(BadgeAnchor::TopLeft))
        .push(Badge::new(" bb", Color::Yellow).anchor(BadgeAnchor::TopLeft))
        .push(Badge::new(" ccc", Color::Cyan).anchor(BadgeAnchor::TopLeft));
    render("stack_top_left_multi", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_stack_top_right_multi() {
    let stack = BadgeStack::new()
        .push(Badge::new(" a", Color::Green).anchor(BadgeAnchor::TopRight))
        .push(Badge::new(" bb", Color::Yellow).anchor(BadgeAnchor::TopRight));
    render("stack_top_right_multi", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_mixed_anchors() {
    let stack = BadgeStack::new()
        .push(Badge::new(" main", Color::Green).anchor(BadgeAnchor::TopLeft))
        .push(Badge::new(" 3 warnings", Color::Yellow).anchor(BadgeAnchor::TopRight))
        .push(Badge::new(" saved", Color::Cyan).anchor(BadgeAnchor::BottomLeft))
        .push(Badge::new(" build: ok", Color::Blue).anchor(BadgeAnchor::BottomRight));
    render("mixed_anchors", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_no_closable() {
    let stack = BadgeStack::new()
        .push(Badge::new(" main", Color::Green).closable(false));
    render("no_closable", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_with_title() {
    let stack = BadgeStack::new()
        .push(Badge::new(" content", Color::Magenta).title("Title"));
    render("with_title", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_dismissed_badge_not_rendered() {
    let mut state = BadgeStackState::default();
    let stack = BadgeStack::new()
        .push(Badge::new(" visible", Color::Green))
        .push(Badge::new(" hidden", Color::Red));
    state.dismiss(1);
    render("dismissed_not_rendered", stack, &mut state);
}

#[test]
fn snapshot_no_border() {
    let stack = BadgeStack::new()
        .push(
            Badge::new(" plain", Color::White)
                .border_type(BorderType::None)
                .closable(false),
        );
    render("no_border", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_centered_top() {
    let stack = BadgeStack::new()
        .push(Badge::new(" centered badge", Color::Green).anchor(BadgeAnchor::Top));
    render("centered_top", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_centered_bottom() {
    let stack = BadgeStack::new()
        .push(Badge::new(" bottom center", Color::Cyan).anchor(BadgeAnchor::Bottom));
    render("centered_bottom", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_left_edge() {
    let stack = BadgeStack::new()
        .push(Badge::new(" side", Color::Yellow).anchor(BadgeAnchor::Left))
        .push(Badge::new(" badge 2", Color::Green).anchor(BadgeAnchor::Left));
    render("left_edge", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_right_edge() {
    let stack = BadgeStack::new()
        .push(Badge::new(" side", Color::Yellow).anchor(BadgeAnchor::Right))
        .push(Badge::new(" badge 2", Color::Green).anchor(BadgeAnchor::Right));
    render("right_edge", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_custom_width_height() {
    let stack = BadgeStack::new()
        .push(
            Badge::new(" tall", Color::Blue)
                .anchor(BadgeAnchor::TopLeft)
                .width(PopupSize::Fixed(10))
                .height(PopupSize::Fixed(5)),
        );
    render("custom_width_height", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_z_index_order() {
    let stack = BadgeStack::new()
        .push(
            Badge::new(" front", Color::Red)
                .anchor(BadgeAnchor::TopLeft)
                .z_index(10),
        )
        .push(
            Badge::new(" back", Color::Green)
                .anchor(BadgeAnchor::TopLeft)
                .z_index(0),
        );
    render("z_index_order", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_badge_with_bg_color() {
    let stack = BadgeStack::new()
        .push(
            Badge::new(" bg", Color::White)
                .bg_color(Color::Blue),
        );
    render("badge_with_bg_color", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_layers_bottom_left() {
    let stack = BadgeStack::new()
        .push(
            Badge::new(" layer 0", Color::Cyan)
                .anchor(BadgeAnchor::BottomLeft)
                .layer(0),
        )
        .push(
            Badge::new(" layer 1", Color::Yellow)
                .anchor(BadgeAnchor::BottomLeft)
                .layer(1),
        )
        .push(
            Badge::new(" also 0", Color::Green)
                .anchor(BadgeAnchor::BottomLeft)
                .layer(0),
        );
    render("layers_bottom_left", stack, &mut BadgeStackState::default());
}

#[test]
fn snapshot_layers_top_right() {
    let stack = BadgeStack::new()
        .push(
            Badge::new(" top", Color::Red)
                .anchor(BadgeAnchor::TopRight)
                .layer(1),
        )
        .push(
            Badge::new(" bottom", Color::Blue)
                .anchor(BadgeAnchor::TopRight)
                .layer(0),
        );
    render("layers_top_right", stack, &mut BadgeStackState::default());
}