use std::collections::HashSet;
use hefesto_widgets::{
ChoosePopup, ChoosePopupState, ConfirmationVariant,
ThemedConfirmationPopup, TreePopup, TreeState, TreeNode, PopupSize,
};
use pandora_kit::popup_rect;
use pandora_kit::popup_config::{PopupConfig, PopupConfigurable};
use ratatui::{
backend::TestBackend,
buffer::Buffer,
layout::Rect,
style::Style,
Terminal,
};
fn items_4() -> Vec<(String, Style)> {
vec![
("Manzana".to_string(), Style::default()),
("Banana".to_string(), Style::default()),
("Cereza".to_string(), Style::default()),
("Durazno".to_string(), Style::default()),
]
}
fn sample_nodes() -> Vec<TreeNode<'static>> {
vec![
TreeNode {
id: 1,
text: ratatui::text::Line::from("src"),
children: vec![
TreeNode {
id: 2,
text: ratatui::text::Line::from("main.rs"),
children: vec![],
},
TreeNode {
id: 3,
text: ratatui::text::Line::from("lib.rs"),
children: vec![],
},
],
},
TreeNode {
id: 4,
text: ratatui::text::Line::from("Cargo.toml"),
children: vec![],
},
]
}
const TOP_LEFT: &[char] = &['╭', '┌'];
const TOP_RIGHT: &[char] = &['╮', '┐'];
const BOTTOM_LEFT: &[char] = &['╰', '└'];
const BOTTOM_RIGHT: &[char] = &['╯', '┘'];
fn actual_rect(buf: &Buffer) -> Option<Rect> {
let area = buf.area();
let w = area.width;
let h = area.height;
let mut top: Option<u16> = None;
let mut bottom: Option<u16> = None;
let mut left: Option<u16> = None;
let mut right: Option<u16> = None;
for y in 0..h {
for x in 0..w {
let ch = buf[(x, y)].symbol().chars().next().unwrap_or(' ');
if TOP_LEFT.contains(&ch) {
top = top.or(Some(y));
left = left.or(Some(x));
}
if TOP_RIGHT.contains(&ch) {
top = top.or(Some(y));
right = right.or(Some(x));
}
if BOTTOM_LEFT.contains(&ch) {
bottom = bottom.or(Some(y));
left = left.or(Some(x));
}
if BOTTOM_RIGHT.contains(&ch) {
bottom = bottom.or(Some(y));
right = right.or(Some(x));
}
}
}
Some(Rect::new(left?, top?, right? - left? + 1, bottom? - top? + 1))
}
fn render_choose(popup: ChoosePopup<'_>) -> Buffer {
let backend = TestBackend::new(60, 20);
let mut terminal = Terminal::new(backend).unwrap();
let mut state = ChoosePopupState::default();
terminal
.draw(|f| f.render_stateful_widget(popup, f.area(), &mut state))
.unwrap();
terminal.backend().buffer().clone()
}
fn render_tree(popup: TreePopup<'_>, state: &mut TreeState) -> Buffer {
let backend = TestBackend::new(60, 20);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| f.render_stateful_widget(popup, f.area(), state))
.unwrap();
terminal.backend().buffer().clone()
}
fn total_visible_tree(nodes: &[TreeNode], expanded: &HashSet<usize>) -> usize {
fn walk(nodes: &[TreeNode], expanded: &HashSet<usize>, count: &mut usize) {
for node in nodes {
*count += 1;
if !node.children.is_empty() && expanded.contains(&node.id) {
walk(&node.children, expanded, count);
}
}
}
let mut count = 0;
walk(nodes, expanded, &mut count);
count
}
#[test]
fn choose_rect_matches_rendered() {
let items = items_4();
let area = Rect::new(0, 0, 60, 20);
let popup_auto = ChoosePopup::new(items.clone());
let pr_auto = popup_auto.resolve_rect(area, &ChoosePopupState::default());
let buf = render_choose(popup_auto.clone());
let actual = actual_rect(&buf).expect("no border corners found");
assert_eq!(
pr_auto, actual,
"auto-height resolve_rect ({pr_auto:?}) does not match rendered ({actual:?})",
);
let h = popup_rect::choose_default_height(items.len());
let pr_fixed = popup_auto
.clone()
.height(PopupSize::Fixed(h))
.resolve_rect(area, &ChoosePopupState::default());
assert_eq!(
pr_fixed, actual,
"fixed-height resolve_rect ({pr_fixed:?}) does not match rendered ({actual:?})",
);
}
#[test]
fn tree_rect_matches_rendered() {
let nodes = sample_nodes();
let area = Rect::new(0, 0, 60, 20);
let mut state = TreeState::default();
state.scroll_state.follow = false;
state.scroll_state.select(Some(0));
let popup_auto = TreePopup::new(nodes.clone()).header();
let pr_auto = popup_auto.resolve_rect(area, &state);
let buf = render_tree(popup_auto.clone(), &mut state);
let actual = actual_rect(&buf).expect("no border corners found");
assert_eq!(
pr_auto, actual,
"auto-height resolve_rect ({pr_auto:?}) does not match rendered ({actual:?})",
);
let visible_count = total_visible_tree(&nodes, &state.expanded);
let h = popup_rect::tree_default_height(visible_count, true);
let pr_fixed = popup_auto
.clone()
.height(PopupSize::Fixed(h))
.resolve_rect(area, &state);
assert_eq!(
pr_fixed, actual,
"fixed-height resolve_rect ({pr_fixed:?}) does not match rendered ({actual:?})",
);
}
#[test]
fn confirm_rect_matches_rendered() {
let area = Rect::new(0, 0, 60, 20);
let popup = ThemedConfirmationPopup::new(
"Test",
vec![ratatui::text::Line::from("Message")],
ConfirmationVariant::None,
)
.options("Sí", "No")
.with_config(&PopupConfig::new());
let pr = popup.resolve_rect(area);
let backend = TestBackend::new(60, 20);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| f.render_widget(popup.clone(), f.area()))
.unwrap();
let actual =
actual_rect(terminal.backend().buffer()).expect("no border corners found");
assert_eq!(
pr, actual,
"confirm resolve_rect ({pr:?}) should match rendered ({actual:?})",
);
}