hefesto-widgets 0.5.0

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::Style, Terminal};
use crate::BorderType;

fn render(name: &str, popup: ConfirmationPopup<'_>) {
    let backend = TestBackend::new(60, 20);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal.draw(|f| f.render_widget(popup, f.area())).unwrap();
    assert_snapshot!(name, terminal.backend());
}

#[test]
fn snapshot_default() {
    render("confirmation_default", ConfirmationPopup::new());
}

#[test]
fn snapshot_with_title() {
    render("confirmation_with_title", ConfirmationPopup::new().title("Confirmar acción"));
}

#[test]
fn snapshot_with_body() {
    render("confirmation_with_body", ConfirmationPopup::new()
        .title("Eliminar archivo")
        .body(vec![
            Line::from("¿Estás seguro de eliminar este archivo?"),
            Line::from("Esta acción no se puede deshacer."),
        ]));
}

#[test]
fn snapshot_custom_options() {
    render("confirmation_custom_options", ConfirmationPopup::new()
        .title("Guardar cambios")
        .body(vec![Line::from("¿Guardar los cambios antes de salir?")])
        .options("Guardar", "Descartar"));
}

#[test]
fn snapshot_custom_colors() {
    render("confirmation_custom_colors", ConfirmationPopup::new()
        .title("Advertencia")
        .body(vec![Line::from("Esta acción es peligrosa.")])
        .border_color(Color::Red)
        .confirm_style(Style::new().bg(Color::Red).fg(Color::Black))
        .cancel_style(Style::new().bg(Color::Blue).fg(Color::Black)));
}

#[test]
fn snapshot_border_type_none() {
    render("confirmation_border_none", ConfirmationPopup::new()
        .title("Sin borde")
        .body(vec![Line::from("No hay borde visible")])
        .border_type(BorderType::None));
}

#[test]
fn snapshot_border_type_double() {
    render("confirmation_border_double", ConfirmationPopup::new()
        .title("Borde doble")
        .body(vec![Line::from("Borde decorativo doble")])
        .border_type(BorderType::Double));
}

#[test]
fn snapshot_with_position() {
    let backend = TestBackend::new(60, 20);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal.draw(|f| {
        let popup = ConfirmationPopup::new()
            .title("Posicionado")
            .body(vec![Line::from("Popup en posición fija")]);
        f.render_widget(popup, f.area());
    }).unwrap();
    assert_snapshot!("confirmation_positioned", terminal.backend());
}

#[test]
fn default_impl_works() {
    let _default = ConfirmationPopup::default();
    let _new = ConfirmationPopup::new();
}

#[test]
fn options_sets_labels() {
    let backend = TestBackend::new(30, 10);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal.draw(|f| {
        let popup = ConfirmationPopup::new()
            .title("Test")
            .body(vec![Line::from("confirm?")])
            .options("Yes", "No");
        f.render_widget(popup, f.area());
    }).unwrap();
}

#[test]
fn body_sets_body() {
    let backend = TestBackend::new(30, 10);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal.draw(|f| {
        let popup = ConfirmationPopup::new()
            .body(vec![Line::from("line1"), Line::from("line2")]);
        f.render_widget(popup, f.area());
    }).unwrap();
}

#[test]
fn position_moves_popup() {
    let backend = TestBackend::new(60, 20);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal.draw(|f| {
        let popup = ConfirmationPopup::new()
            .body(vec![Line::from("moved")])
            .position(50, 10)
            .width(PopupSize::Fixed(10))
            .height(PopupSize::Fixed(5));
        f.render_widget(popup, f.area());
    }).unwrap();
}

#[test]
fn position_clamps_to_area() {
    let backend = TestBackend::new(60, 20);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal.draw(|f| {
        let popup = ConfirmationPopup::new()
            .title("Fuera de bounds")
            .position(50, 15)
            .width(PopupSize::Fixed(20))
            .height(PopupSize::Fixed(10));
        f.render_widget(popup, f.area());
    }).unwrap();
}

#[test]
fn border_color_applies_to_border_cells() {
    let mut buf = Buffer::empty(Rect::new(0, 0, 60, 20));
    let popup = ConfirmationPopup::new()
        .title("Color test")
        .body(vec![Line::from("test")])
        .border_color(Color::Red)
        .width(PopupSize::Fixed(20))
        .height(PopupSize::Fixed(5))
        .position(0, 0);
    popup.render(buf.area, &mut buf);
    let corner_style = buf[(0, 0)].style();
    assert_eq!(corner_style.fg, Some(Color::Red));
}

// ── bg_color ──

#[test]
fn snapshot_bg_color() {
    render("confirmation_bg_color", ConfirmationPopup::new()
        .title("Confirmar acción")
        .body(vec![
            Line::from("¿Está seguro de eliminar este archivo?"),
            Line::from("Esta acción no se puede deshacer."),
        ])
        .bg_color(Color::Blue));
}

// ── resolve_rect ──

#[test]
fn resolve_rect_delegates_to_popup() {
    let popup = ConfirmationPopup::new()
        .width(PopupSize::Fixed(30))
        .height(PopupSize::Fixed(10));
    assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).width, 30);
    assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).height, 10);
}

#[test]
fn resolve_rect_clamps_when_larger_than_area() {
    let popup = ConfirmationPopup::new()
        .width(PopupSize::Fixed(100))
        .height(PopupSize::Fixed(50));
    assert_eq!(
        popup.resolve_rect(Rect::new(0, 0, 60, 20)),
        Rect::new(0, 0, 60, 20)
    );
}