use super::*;
use insta::assert_snapshot;
use ratatui::{backend::TestBackend, Terminal};
use crate::{Badge, BadgeAnchor, BadgeStack};
fn render(name: &str, popup: Popup<'_>) {
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 resolve_rect_width_auto() {
let popup = Popup::new(Color::White).height(PopupSize::Fixed(10));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).width, 80);
}
#[test]
fn resolve_rect_width_fixed() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Fixed(10));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).width, 30);
}
#[test]
fn resolve_rect_width_percent() {
let popup = Popup::new(Color::White)
.width(PopupSize::Percent(50))
.height(PopupSize::Fixed(10));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).width, 50);
}
#[test]
fn resolve_rect_width_max_caps_to_max() {
let popup = Popup::new(Color::White)
.width(PopupSize::Max(30))
.height(PopupSize::Fixed(10));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).width, 30);
}
#[test]
fn resolve_rect_width_max_under_threshold() {
let popup = Popup::new(Color::White)
.width(PopupSize::Max(200))
.height(PopupSize::Fixed(10));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).width, 80);
}
#[test]
fn resolve_rect_height_auto() {
let popup = Popup::new(Color::White);
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).height, 35);
}
#[test]
fn resolve_rect_height_fixed() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Fixed(10));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).height, 10);
}
#[test]
fn resolve_rect_height_percent() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Percent(50));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).height, 25);
}
#[test]
fn resolve_rect_height_max_caps_to_max() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Max(20));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).height, 20);
}
#[test]
fn resolve_rect_height_max_under_threshold() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Max(200));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).height, 35);
}
#[test]
fn resolve_rect_width_max_at_threshold() {
let popup = Popup::new(Color::White)
.width(PopupSize::Max(80))
.height(PopupSize::Fixed(10));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).width, 80);
}
#[test]
fn resolve_rect_height_max_at_threshold() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(10))
.height(PopupSize::Max(35));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)).height, 35);
}
#[test]
fn content_offset_with_header() {
let popup = Popup::new(Color::White).header();
assert_eq!(popup.content_offset(), 3);
}
#[test]
fn content_offset_default_padding() {
let popup = Popup::new(Color::White);
assert_eq!(popup.content_offset(), 2);
}
#[test]
fn content_offset_custom_padding() {
let popup = Popup::new(Color::White).padding(3);
assert_eq!(popup.content_offset(), 4);
}
#[test]
fn content_offset_zero_padding_with_header() {
let popup = Popup::new(Color::White).padding(0).header();
assert_eq!(popup.content_offset(), 3);
}
#[test]
fn resolve_rect_position_overrides_origin_and_centering() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5))
.position(3, 7);
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)), Rect::new(3, 7, 10, 5));
}
#[test]
fn resolve_rect_origin_overrides_centering() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5))
.origin(5, 5);
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)), Rect::new(5, 5, 10, 5));
}
#[test]
fn resolve_rect_centered_when_neither_set() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 100, 50)), Rect::new(45, 22, 10, 5));
}
#[test]
fn resolve_rect_centered_clamps_when_popup_larger_than_area() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(100))
.height(PopupSize::Fixed(50));
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 50, 20)), Rect::new(0, 0, 50, 20));
}
#[test]
fn resolve_rect_origin_clamps_oversized_size() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(200))
.height(PopupSize::Fixed(100))
.origin(5, 5);
let r = popup.resolve_rect(Rect::new(0, 0, 60, 20));
assert_eq!(r, Rect::new(5, 5, 55, 15));
}
#[test]
fn resolve_rect_origin_out_of_bounds_keeps_position() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5))
.origin(80, 30);
assert_eq!(popup.resolve_rect(Rect::new(0, 0, 60, 20)), Rect::new(59, 19, 1, 1));
}
#[test]
fn helper_has_border_none() {
assert!(!BorderType::None.has_border());
}
#[test]
fn helper_has_border_plain() {
assert!(BorderType::Plain.has_border());
}
#[test]
fn helper_has_border_rounded() {
assert!(BorderType::Rounded.has_border());
}
#[test]
fn helper_has_border_double() {
assert!(BorderType::Double.has_border());
}
#[test]
fn helper_has_border_thick() {
assert!(BorderType::Thick.has_border());
}
#[test]
fn helper_has_border_quadrant_inside() {
assert!(BorderType::QuadrantInside.has_border());
}
#[test]
fn helper_has_border_quadrant_outside() {
assert!(BorderType::QuadrantOutside.has_border());
}
#[test]
fn render_clears_target_area() {
let mut buf = Buffer::empty(Rect::new(0, 0, 60, 20));
for y in 0..20 {
for x in 0..60 {
buf[(x, y)].set_symbol("X");
}
}
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5))
.position(10, 5);
popup.render(buf.area, &mut buf);
for y in 5..10 {
for x in 10..20 {
assert_ne!(buf[(x, y)].symbol(), "X", "cell ({},{}) should be cleared", x, y);
}
}
assert_eq!(buf[(0, 0)].symbol(), "X");
assert_eq!(buf[(59, 19)].symbol(), "X");
}
#[test]
fn render_header_writes_title_into_area() {
let area = Rect::new(0, 0, 20, 2);
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 2));
Popup::render_header(area, &mut buf, Color::White, "HI");
assert_ne!(buf[(0, 1)].symbol(), " ", "bottom border should be visible");
assert_eq!(buf[(1, 0)].symbol(), "H");
assert_eq!(buf[(2, 0)].symbol(), "I");
}
#[test]
fn render_inner_returns_inner_content_area() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5))
.padding(0)
.position(0, 0);
let mut buf = Buffer::empty(Rect::new(0, 0, 30, 15));
let inner = popup.render_inner(Rect::new(0, 0, 30, 15), &mut buf);
assert_eq!(inner, Rect::new(1, 1, 8, 3));
}
#[test]
fn render_inner_with_header_returns_content_chunk() {
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(10))
.padding(0)
.header()
.position(0, 0);
let mut buf = Buffer::empty(Rect::new(0, 0, 30, 15));
let inner = popup.render_inner(Rect::new(0, 0, 30, 15), &mut buf);
assert_eq!(inner, Rect::new(1, 3, 18, 6));
}
#[test]
fn border_color_applies_to_border_cells() {
let popup = Popup::new(Color::Red)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5))
.position(0, 0);
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 10));
popup.render(buf.area, &mut buf);
let corner_style = buf[(0, 0)].style();
assert_eq!(corner_style.fg, Some(Color::Red));
}
const SNAP_AREA: (u16, u16) = (10, 5);
fn snap_border(name: &str, bt: BorderType) {
render(name, Popup::new(Color::White)
.width(PopupSize::Fixed(SNAP_AREA.0))
.height(PopupSize::Fixed(SNAP_AREA.1))
.position(10, 5)
.border_type(bt));
}
#[test]
fn snapshot_border_none() { snap_border("border_none", BorderType::None); }
#[test]
fn snapshot_border_plain() { snap_border("border_plain", BorderType::Plain); }
#[test]
fn snapshot_border_rounded() { snap_border("border_rounded", BorderType::Rounded); }
#[test]
fn snapshot_border_double() { snap_border("border_double", BorderType::Double); }
#[test]
fn snapshot_border_thick() { snap_border("border_thick", BorderType::Thick); }
#[test]
fn snapshot_border_quadrant_inside() { snap_border("border_quadrant_inside", BorderType::QuadrantInside); }
#[test]
fn snapshot_border_quadrant_outside() { snap_border("border_quadrant_outside", BorderType::QuadrantOutside); }
#[test]
fn snapshot_title() {
render("title", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(5))
.position(5, 5)
.title("HI"));
}
#[test]
fn snapshot_header_mode() {
render("header_mode", Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Fixed(8))
.position(5, 5)
.header()
.title("HEADER"));
}
#[test]
fn snapshot_with_content() {
render("with_content", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(5))
.position(5, 5)
.content(vec![Line::from("Hola mundo")]));
}
#[test]
fn snapshot_multiline_content() {
render("multiline_content", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(8))
.position(5, 5)
.content(vec![
Line::from("Linea uno"),
Line::from("Linea dos"),
Line::from("Linea tres"),
]));
}
#[test]
fn snapshot_empty_custom_message() {
render("empty_custom_message", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(5))
.position(5, 5)
.empty_message("No hay datos"));
}
#[test]
fn snapshot_no_border_with_title() {
render("no_border_with_title", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(5))
.position(5, 5)
.border_type(BorderType::None)
.title("OCULTO"));
}
#[test]
fn snapshot_custom_padding() {
render("custom_padding", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(10))
.position(5, 5)
.padding(3)
.content(vec![Line::from("X")]));
}
#[test]
fn snapshot_bg_color_with_border() {
render("bg_color_with_border", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(8))
.position(5, 5)
.bg_color(Color::Blue)
.content(vec![Line::from("Texto con fondo")]));
}
#[test]
fn snapshot_bg_color_no_border() {
render("bg_color_no_border", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(8))
.position(5, 5)
.border_type(BorderType::None)
.bg_color(Color::Blue)
.content(vec![Line::from("Sin borde con fondo")]));
}
#[test]
fn snapshot_bg_color_with_title() {
render("bg_color_with_title", Popup::new(Color::White)
.width(PopupSize::Fixed(20))
.height(PopupSize::Fixed(8))
.position(5, 5)
.bg_color(Color::Blue)
.title("Titulo")
.content(vec![Line::from("Texto")]));
}
#[test]
fn border_color_and_bg_color_coexist() {
let popup = Popup::new(Color::Red)
.width(PopupSize::Fixed(10))
.height(PopupSize::Fixed(5))
.position(0, 0)
.bg_color(Color::Blue);
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 10));
popup.render(buf.area, &mut buf);
let corner_style = buf[(0, 0)].style();
assert_eq!(corner_style.fg, Some(Color::Red));
assert_eq!(corner_style.bg, Some(Color::Blue));
}
#[test]
fn content_offset_no_border_no_title() {
let popup = Popup::new(Color::White)
.border_type(BorderType::None)
.padding(1);
assert_eq!(popup.content_offset(), 1);
}
#[test]
fn content_offset_no_border_with_title() {
let popup = Popup::new(Color::White)
.border_type(BorderType::None)
.title("X");
assert_eq!(popup.content_offset(), 2); }
#[test]
fn content_offset_border_with_title() {
let popup = Popup::new(Color::White)
.title("X");
assert_eq!(popup.content_offset(), 2); }
#[test]
fn content_offset_header_mode_no_border() {
let popup = Popup::new(Color::White)
.border_type(BorderType::None)
.header()
.title("X");
assert_eq!(popup.content_offset(), 2); }
#[test]
fn snapshot_with_badges() {
let badges = BadgeStack::new()
.push(Badge::new(" main", Color::Green).anchor(BadgeAnchor::TopLeft));
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Fixed(5))
.title("Popup")
.badges(badges);
render("popup_with_badges", popup);
}
#[test]
fn snapshot_badges_on_top() {
let badges = BadgeStack::new()
.push(Badge::new(" on top", Color::Red).anchor(BadgeAnchor::TopLeft));
let popup = Popup::new(Color::White)
.width(PopupSize::Fixed(30))
.height(PopupSize::Fixed(5))
.title("Popup")
.badges(badges)
.z_index(0);
render("popup_badges_on_top", popup);
}