use alloc::vec::Vec;
use itertools::Itertools;
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Alignment, Rect};
use ratatui_core::style::{Style, Styled};
use ratatui_core::symbols::border;
use ratatui_core::symbols::merge::MergeStrategy;
use ratatui_core::text::Line;
use ratatui_core::widgets::Widget;
use strum::{Display, EnumString};
pub use self::padding::Padding;
use crate::borders::{BorderType, Borders};
mod padding;
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Block<'a> {
titles: Vec<(Option<TitlePosition>, Line<'a>)>,
titles_style: Style,
titles_alignment: Alignment,
titles_position: TitlePosition,
borders: Borders,
border_style: Style,
border_set: border::Set<'a>,
style: Style,
padding: Padding,
merge_borders: MergeStrategy,
}
#[derive(Debug, Default, Display, EnumString, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TitlePosition {
#[default]
Top,
Bottom,
}
impl<'a> Block<'a> {
pub const fn new() -> Self {
Self {
titles: Vec::new(),
titles_style: Style::new(),
titles_alignment: Alignment::Left,
titles_position: TitlePosition::Top,
borders: Borders::NONE,
border_style: Style::new(),
border_set: BorderType::Plain.to_border_set(),
style: Style::new(),
padding: Padding::ZERO,
merge_borders: MergeStrategy::Replace,
}
}
pub const fn bordered() -> Self {
let mut block = Self::new();
block.borders = Borders::ALL;
block
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title<T>(mut self, title: T) -> Self
where
T: Into<Line<'a>>,
{
self.titles.push((None, title.into()));
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
let line = title.into();
self.titles.push((Some(TitlePosition::Top), line));
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
let line = title.into();
self.titles.push((Some(TitlePosition::Bottom), line));
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
self.titles_style = style.into();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
self.titles_alignment = alignment;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn title_position(mut self, position: TitlePosition) -> Self {
self.titles_position = position;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
self.border_style = style.into();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
self.style = style.into();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn borders(mut self, flag: Borders) -> Self {
self.borders = flag;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn border_type(mut self, border_type: BorderType) -> Self {
self.border_set = border_type.to_border_set();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn border_set(mut self, border_set: border::Set<'a>) -> Self {
self.border_set = border_set;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn merge_borders(mut self, strategy: MergeStrategy) -> Self {
self.merge_borders = strategy;
self
}
pub fn inner(&self, area: Rect) -> Rect {
let mut inner = area;
if self.borders.intersects(Borders::LEFT) {
inner.x = inner.x.saturating_add(1).min(inner.right());
inner.width = inner.width.saturating_sub(1);
}
if self.borders.intersects(Borders::TOP) || self.has_title_at_position(TitlePosition::Top) {
inner.y = inner.y.saturating_add(1).min(inner.bottom());
inner.height = inner.height.saturating_sub(1);
}
if self.borders.intersects(Borders::RIGHT) {
inner.width = inner.width.saturating_sub(1);
}
if self.borders.intersects(Borders::BOTTOM)
|| self.has_title_at_position(TitlePosition::Bottom)
{
inner.height = inner.height.saturating_sub(1);
}
inner.x = inner.x.saturating_add(self.padding.left);
inner.y = inner.y.saturating_add(self.padding.top);
inner.width = inner
.width
.saturating_sub(self.padding.left + self.padding.right);
inner.height = inner
.height
.saturating_sub(self.padding.top + self.padding.bottom);
inner
}
fn has_title_at_position(&self, position: TitlePosition) -> bool {
self.titles
.iter()
.any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
}
}
impl Widget for Block<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
Widget::render(&self, area, buf);
}
}
impl Widget for &Block<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
if area.is_empty() {
return;
}
buf.set_style(area, self.style);
self.render_borders(area, buf);
self.render_titles(area, buf);
}
}
impl Block<'_> {
fn render_borders(&self, area: Rect, buf: &mut Buffer) {
self.render_sides(area, buf);
self.render_corners(area, buf);
}
fn render_sides(&self, area: Rect, buf: &mut Buffer) {
let left = area.left();
let top = area.top();
let right = area.right() - 1;
let bottom = area.bottom() - 1;
let is_replace = self.merge_borders != MergeStrategy::Replace;
let left_inset = left + u16::from(is_replace && self.borders.contains(Borders::LEFT));
let top_inset = top + u16::from(is_replace && self.borders.contains(Borders::TOP));
let right_inset = right - u16::from(is_replace && self.borders.contains(Borders::RIGHT));
let bottom_inset = bottom - u16::from(is_replace && self.borders.contains(Borders::BOTTOM));
let sides = [
(
Borders::LEFT,
left..=left,
top_inset..=bottom_inset,
self.border_set.vertical_left,
),
(
Borders::TOP,
left_inset..=right_inset,
top..=top,
self.border_set.horizontal_top,
),
(
Borders::RIGHT,
right..=right,
top_inset..=bottom_inset,
self.border_set.vertical_right,
),
(
Borders::BOTTOM,
left_inset..=right_inset,
bottom..=bottom,
self.border_set.horizontal_bottom,
),
];
for (border, x_range, y_range, symbol) in sides {
if self.borders.contains(border) {
for x in x_range {
for y in y_range.clone() {
buf[(x, y)]
.merge_symbol(symbol, self.merge_borders)
.set_style(self.border_style);
}
}
}
}
}
fn render_corners(&self, area: Rect, buf: &mut Buffer) {
let corners = [
(
Borders::RIGHT | Borders::BOTTOM,
area.right() - 1,
area.bottom() - 1,
self.border_set.bottom_right,
),
(
Borders::RIGHT | Borders::TOP,
area.right() - 1,
area.top(),
self.border_set.top_right,
),
(
Borders::LEFT | Borders::BOTTOM,
area.left(),
area.bottom() - 1,
self.border_set.bottom_left,
),
(
Borders::LEFT | Borders::TOP,
area.left(),
area.top(),
self.border_set.top_left,
),
];
for (border, x, y, symbol) in corners {
if self.borders.contains(border) {
buf[(x, y)]
.merge_symbol(symbol, self.merge_borders)
.set_style(self.border_style);
}
}
}
fn render_titles(&self, area: Rect, buf: &mut Buffer) {
self.render_title_position(TitlePosition::Top, area, buf);
self.render_title_position(TitlePosition::Bottom, area, buf);
}
fn render_title_position(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
self.render_left_titles(position, area, buf);
self.render_center_titles(position, area, buf);
self.render_right_titles(position, area, buf);
}
#[expect(clippy::similar_names)]
fn render_right_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
let titles = self.filtered_titles(position, Alignment::Right);
let mut titles_area = self.titles_area(area, position);
for title in titles.rev() {
if titles_area.is_empty() {
break;
}
let title_width = title.width() as u16;
let title_area = Rect {
x: titles_area
.right()
.saturating_sub(title_width)
.max(titles_area.left()),
width: title_width.min(titles_area.width),
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.render(title_area, buf);
titles_area.width = titles_area
.width
.saturating_sub(title_width)
.saturating_sub(1); }
}
fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
let area = self.titles_area(area, position);
let titles = self
.filtered_titles(position, Alignment::Center)
.collect_vec();
let total_width = titles
.iter()
.map(|title| title.width() as u16 + 1)
.sum::<u16>()
.saturating_sub(1);
if total_width <= area.width {
self.render_centered_titles_without_truncation(titles, total_width, area, buf);
} else {
self.render_centered_titles_with_truncation(titles, total_width, area, buf);
}
}
fn render_centered_titles_without_truncation(
&self,
titles: Vec<&Line<'_>>,
total_width: u16,
area: Rect,
buf: &mut Buffer,
) {
let x = area.left() + area.width.saturating_sub(total_width) / 2;
let mut area = Rect { x, ..area };
for title in titles {
let width = title.width() as u16;
let title_area = Rect { width, ..area };
buf.set_style(title_area, self.titles_style);
title.render(title_area, buf);
area.x = area.x.saturating_add(width + 1);
area.width = area.width.saturating_sub(width + 1);
}
}
fn render_centered_titles_with_truncation(
&self,
titles: Vec<&Line<'_>>,
total_width: u16,
mut area: Rect,
buf: &mut Buffer,
) {
let mut offset = total_width.saturating_sub(area.width) / 2;
for title in titles {
if area.is_empty() {
break;
}
let width = area.width.min(title.width() as u16).saturating_sub(offset);
let title_area = Rect { width, ..area };
buf.set_style(title_area, self.titles_style);
if offset > 0 {
title.clone().right_aligned().render(title_area, buf);
offset = offset.saturating_sub(width).saturating_sub(1);
} else {
title.clone().left_aligned().render(title_area, buf);
}
area.x = area.x.saturating_add(width + 1);
area.width = area.width.saturating_sub(width + 1);
}
}
#[expect(clippy::similar_names)]
fn render_left_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
let titles = self.filtered_titles(position, Alignment::Left);
let mut titles_area = self.titles_area(area, position);
for title in titles {
if titles_area.is_empty() {
break;
}
let title_width = title.width() as u16;
let title_area = Rect {
width: title_width.min(titles_area.width),
..titles_area
};
buf.set_style(title_area, self.titles_style);
title.render(title_area, buf);
titles_area.x = titles_area.x.saturating_add(title_width + 1);
titles_area.width = titles_area.width.saturating_sub(title_width + 1);
}
}
fn filtered_titles(
&self,
position: TitlePosition,
alignment: Alignment,
) -> impl DoubleEndedIterator<Item = &Line<'_>> {
self.titles
.iter()
.filter(move |(pos, _)| pos.unwrap_or(self.titles_position) == position)
.filter(move |(_, line)| line.alignment.unwrap_or(self.titles_alignment) == alignment)
.map(|(_, line)| line)
}
fn titles_area(&self, area: Rect, position: TitlePosition) -> Rect {
let left_border = u16::from(self.borders.contains(Borders::LEFT));
let right_border = u16::from(self.borders.contains(Borders::RIGHT));
Rect {
x: area.left() + left_border,
y: match position {
TitlePosition::Top => area.top(),
TitlePosition::Bottom => area.bottom() - 1,
},
width: area
.width
.saturating_sub(left_border)
.saturating_sub(right_border),
height: 1,
}
}
pub(crate) fn horizontal_space(&self) -> (u16, u16) {
let left = self
.padding
.left
.saturating_add(u16::from(self.borders.contains(Borders::LEFT)));
let right = self
.padding
.right
.saturating_add(u16::from(self.borders.contains(Borders::RIGHT)));
(left, right)
}
pub(crate) fn vertical_space(&self) -> (u16, u16) {
let has_top =
self.borders.contains(Borders::TOP) || self.has_title_at_position(TitlePosition::Top);
let top = self.padding.top + u16::from(has_top);
let has_bottom = self.borders.contains(Borders::BOTTOM)
|| self.has_title_at_position(TitlePosition::Bottom);
let bottom = self.padding.bottom + u16::from(has_bottom);
(top, bottom)
}
}
pub trait BlockExt {
fn inner_if_some(&self, area: Rect) -> Rect;
}
impl BlockExt for Option<Block<'_>> {
fn inner_if_some(&self, area: Rect) -> Rect {
self.as_ref().map_or(area, |block| block.inner(area))
}
}
impl Styled for Block<'_> {
type Item = Self;
fn style(&self) -> Style {
self.style
}
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
self.style(style)
}
}
#[cfg(test)]
mod tests {
use alloc::{format, vec};
use itertools::iproduct;
use ratatui_core::layout::Offset;
use ratatui_core::style::{Color, Modifier, Stylize};
use rstest::rstest;
use strum::ParseError;
use super::*;
#[test]
fn create_with_all_borders() {
let block = Block::bordered();
assert_eq!(block.borders, Borders::all());
}
#[rstest]
#[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
#[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
#[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
#[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
#[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
#[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
#[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
#[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
#[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
#[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
#[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
#[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
#[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
#[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
#[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
#[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
#[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
#[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
#[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
#[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
#[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
#[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
fn inner_takes_into_account_the_borders(
#[case] borders: Borders,
#[case] area: Rect,
#[case] expected: Rect,
) {
let block = Block::new().borders(borders);
assert_eq!(block.inner(area), expected);
}
#[rstest]
#[case::left(Alignment::Left)]
#[case::center(Alignment::Center)]
#[case::right(Alignment::Right)]
fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
let area = Rect::new(0, 0, 0, 1);
let expected = Rect::new(0, 1, 0, 0);
let block = Block::new().title(Line::from("Test").alignment(alignment));
assert_eq!(block.inner(area), expected);
}
#[rstest]
#[case::top_top(Block::new().title_top("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 1))]
#[case::top_bot(Block::new().title_top("Test").borders(Borders::BOTTOM), Rect::new(0, 1, 0, 0))]
#[case::bot_top(Block::new().title_bottom("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 0))]
#[case::bot_bot(Block::new().title_bottom("Test").borders(Borders::BOTTOM), Rect::new(0, 0, 0, 1))]
fn inner_takes_into_account_border_and_title(#[case] block: Block, #[case] expected: Rect) {
let area = Rect::new(0, 0, 0, 2);
assert_eq!(block.inner(area), expected);
}
#[test]
fn has_title_at_position_takes_into_account_all_positioning_declarations() {
let block = Block::new();
assert!(!block.has_title_at_position(TitlePosition::Top));
assert!(!block.has_title_at_position(TitlePosition::Bottom));
let block = Block::new().title_top("test");
assert!(block.has_title_at_position(TitlePosition::Top));
assert!(!block.has_title_at_position(TitlePosition::Bottom));
let block = Block::new().title_bottom("test");
assert!(!block.has_title_at_position(TitlePosition::Top));
assert!(block.has_title_at_position(TitlePosition::Bottom));
let block = Block::new().title_top("test").title_bottom("test");
assert!(block.has_title_at_position(TitlePosition::Top));
assert!(block.has_title_at_position(TitlePosition::Bottom));
}
#[rstest]
#[case::none(Borders::NONE, (0, 0))]
#[case::top(Borders::TOP, (1, 0))]
#[case::right(Borders::RIGHT, (0, 0))]
#[case::bottom(Borders::BOTTOM, (0, 1))]
#[case::left(Borders::LEFT, (0, 0))]
#[case::top_right(Borders::TOP | Borders::RIGHT, (1, 0))]
#[case::top_bottom(Borders::TOP | Borders::BOTTOM, (1, 1))]
#[case::top_left(Borders::TOP | Borders::LEFT, (1, 0))]
#[case::bottom_right(Borders::BOTTOM | Borders::RIGHT, (0, 1))]
#[case::bottom_left(Borders::BOTTOM | Borders::LEFT, (0, 1))]
#[case::left_right(Borders::LEFT | Borders::RIGHT, (0, 0))]
fn vertical_space_takes_into_account_borders(
#[case] borders: Borders,
#[case] vertical_space: (u16, u16),
) {
let block = Block::new().borders(borders);
assert_eq!(block.vertical_space(), vertical_space);
}
#[rstest]
#[case::top_border_top_p1(Borders::TOP, Padding::new(0, 0, 1, 0), (2, 0))]
#[case::right_border_top_p1(Borders::RIGHT, Padding::new(0, 0, 1, 0), (1, 0))]
#[case::bottom_border_top_p1(Borders::BOTTOM, Padding::new(0, 0, 1, 0), (1, 1))]
#[case::left_border_top_p1(Borders::LEFT, Padding::new(0, 0, 1, 0), (1, 0))]
#[case::top_bottom_border_all_p3(Borders::TOP | Borders::BOTTOM, Padding::new(100, 100, 4, 5), (5, 6))]
#[case::no_border(Borders::NONE, Padding::new(100, 100, 10, 13), (10, 13))]
#[case::all(Borders::ALL, Padding::new(100, 100, 1, 3), (2, 4))]
fn vertical_space_takes_into_account_padding(
#[case] borders: Borders,
#[case] padding: Padding,
#[case] vertical_space: (u16, u16),
) {
let block = Block::new().borders(borders).padding(padding);
assert_eq!(block.vertical_space(), vertical_space);
}
#[test]
fn vertical_space_takes_into_account_titles() {
let block = Block::new().title_top("Test");
assert_eq!(block.vertical_space(), (1, 0));
let block = Block::new().title_bottom("Test");
assert_eq!(block.vertical_space(), (0, 1));
}
#[rstest]
#[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Top, (1, 0))]
#[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Top, (1, 0))]
#[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Top, (1, 1))]
#[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Top, (1, 0))]
#[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Bottom, (1, 1))]
#[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Bottom, (0, 1))]
#[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Bottom, (0, 1))]
#[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Bottom, (0, 1))]
fn vertical_space_takes_into_account_borders_and_title(
#[case] block: Block,
#[case] borders: Borders,
#[case] pos: TitlePosition,
#[case] vertical_space: (u16, u16),
) {
let block = block.borders(borders).title_position(pos).title("Test");
assert_eq!(block.vertical_space(), vertical_space);
}
#[test]
fn horizontal_space_takes_into_account_borders() {
let block = Block::bordered();
assert_eq!(block.horizontal_space(), (1, 1));
let block = Block::new().borders(Borders::LEFT);
assert_eq!(block.horizontal_space(), (1, 0));
let block = Block::new().borders(Borders::RIGHT);
assert_eq!(block.horizontal_space(), (0, 1));
}
#[test]
fn horizontal_space_takes_into_account_padding() {
let block = Block::new().padding(Padding::new(1, 1, 100, 100));
assert_eq!(block.horizontal_space(), (1, 1));
let block = Block::new().padding(Padding::new(3, 5, 0, 0));
assert_eq!(block.horizontal_space(), (3, 5));
let block = Block::new().padding(Padding::new(0, 1, 100, 100));
assert_eq!(block.horizontal_space(), (0, 1));
let block = Block::new().padding(Padding::new(1, 0, 100, 100));
assert_eq!(block.horizontal_space(), (1, 0));
}
#[rstest]
#[case::all_bordered_all_padded(Block::bordered(), Padding::new(1, 1, 1, 1), (2, 2))]
#[case::all_bordered_left_padded(Block::bordered(), Padding::new(1, 0, 0, 0), (2, 1))]
#[case::all_bordered_right_padded(Block::bordered(), Padding::new(0, 1, 0, 0), (1, 2))]
#[case::all_bordered_top_padded(Block::bordered(), Padding::new(0, 0, 1, 0), (1, 1))]
#[case::all_bordered_bottom_padded(Block::bordered(), Padding::new(0, 0, 0, 1), (1, 1))]
#[case::left_bordered_left_padded(Block::new().borders(Borders::LEFT), Padding::new(1, 0, 0, 0), (2, 0))]
#[case::left_bordered_right_padded(Block::new().borders(Borders::LEFT), Padding::new(0, 1, 0, 0), (1, 1))]
#[case::right_bordered_right_padded(Block::new().borders(Borders::RIGHT), Padding::new(0, 1, 0, 0), (0, 2))]
#[case::right_bordered_left_padded(Block::new().borders(Borders::RIGHT), Padding::new(1, 0, 0, 0), (1, 1))]
fn horizontal_space_takes_into_account_borders_and_padding(
#[case] block: Block,
#[case] padding: Padding,
#[case] horizontal_space: (u16, u16),
) {
let block = block.padding(padding);
assert_eq!(block.horizontal_space(), horizontal_space);
}
#[test]
const fn border_type_can_be_const() {
const _PLAIN: border::Set = BorderType::border_symbols(BorderType::Plain);
}
#[test]
fn block_new() {
assert_eq!(
Block::new(),
Block {
titles: Vec::new(),
titles_style: Style::new(),
titles_alignment: Alignment::Left,
titles_position: TitlePosition::Top,
borders: Borders::NONE,
border_style: Style::new(),
border_set: BorderType::Plain.to_border_set(),
style: Style::new(),
padding: Padding::ZERO,
merge_borders: MergeStrategy::Replace,
}
);
}
#[test]
const fn block_can_be_const() {
const _DEFAULT_STYLE: Style = Style::new();
const _DEFAULT_PADDING: Padding = Padding::uniform(1);
const _DEFAULT_BLOCK: Block = Block::bordered()
.title_alignment(Alignment::Left)
.title_position(TitlePosition::Top)
.padding(_DEFAULT_PADDING);
}
#[test]
fn style_into_works_from_user_view() {
let block = Block::new().style(Style::new().red());
assert_eq!(block.style, Style::new().red());
let block = Block::new().style(Color::Red);
assert_eq!(block.style, Style::new().red());
let block = Block::new().style((Color::Red, Color::Blue));
assert_eq!(block.style, Style::new().red().on_blue());
let block = Block::new().style(Modifier::BOLD | Modifier::ITALIC);
assert_eq!(block.style, Style::new().bold().italic());
let block = Block::new().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
assert_eq!(block.style, Style::new().bold().italic().not_dim());
let block = Block::new().style((Color::Red, Modifier::BOLD));
assert_eq!(block.style, Style::new().red().bold());
let block = Block::new().style((Color::Red, Color::Blue, Modifier::BOLD));
assert_eq!(block.style, Style::new().red().on_blue().bold());
let block = Block::new().style((
Color::Red,
Color::Blue,
Modifier::BOLD | Modifier::ITALIC,
Modifier::DIM,
));
assert_eq!(
block.style,
Style::new().red().on_blue().bold().italic().not_dim()
);
}
#[test]
fn can_be_stylized() {
let block = Block::new().black().on_white().bold().not_dim();
assert_eq!(
block.style,
Style::default()
.fg(Color::Black)
.bg(Color::White)
.add_modifier(Modifier::BOLD)
.remove_modifier(Modifier::DIM)
);
}
#[test]
fn title_top_bottom() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
Block::bordered()
.title_top(Line::raw("A").left_aligned())
.title_top(Line::raw("B").centered())
.title_top(Line::raw("C").right_aligned())
.title_bottom(Line::raw("D").left_aligned())
.title_bottom(Line::raw("E").centered())
.title_bottom(Line::raw("F").right_aligned())
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌A───B───C┐",
"│ │",
"└D───E───F┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn title_alignment() {
let tests = vec![
(Alignment::Left, "test "),
(Alignment::Center, " test "),
(Alignment::Right, " test"),
];
for (alignment, expected) in tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
Block::new()
.title_alignment(alignment)
.title("test")
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
}
#[test]
fn title_alignment_overrides_block_title_alignment() {
let tests = vec![
(Alignment::Right, Alignment::Left, "test "),
(Alignment::Left, Alignment::Center, " test "),
(Alignment::Center, Alignment::Right, " test"),
];
for (block_title_alignment, alignment, expected) in tests {
let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
Block::new()
.title_alignment(block_title_alignment)
.title(Line::from("test").alignment(alignment))
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([expected]));
}
}
#[test]
fn render_right_aligned_empty_title() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
Block::new()
.title_alignment(Alignment::Right)
.title("")
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([" "; 3]));
}
#[test]
fn title_position() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
Block::new()
.title_position(TitlePosition::Bottom)
.title("test")
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([" ", "test"]));
}
#[test]
fn title_content_style() {
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
Block::new()
.title_alignment(alignment)
.title("test".yellow())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
}
}
#[test]
fn block_title_style() {
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
Block::new()
.title_alignment(alignment)
.title_style(Style::new().yellow())
.title("test")
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
}
}
#[test]
fn title_style_overrides_block_title_style() {
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
Block::new()
.title_alignment(alignment)
.title_style(Style::new().green().on_red())
.title("test".yellow())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
}
}
#[test]
fn title_border_style() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.title("test")
.border_style(Style::new().yellow())
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let mut expected = Buffer::with_lines([
"┌test────┐",
"│ │",
"└────────┘",
]);
expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
assert_eq!(buffer, expected);
}
#[test]
fn border_type_to_string() {
assert_eq!(format!("{}", BorderType::Plain), "Plain");
assert_eq!(format!("{}", BorderType::Rounded), "Rounded");
assert_eq!(format!("{}", BorderType::Double), "Double");
assert_eq!(format!("{}", BorderType::Thick), "Thick");
assert_eq!(
format!("{}", BorderType::LightDoubleDashed),
"LightDoubleDashed"
);
assert_eq!(
format!("{}", BorderType::HeavyDoubleDashed),
"HeavyDoubleDashed"
);
assert_eq!(
format!("{}", BorderType::LightTripleDashed),
"LightTripleDashed"
);
assert_eq!(
format!("{}", BorderType::HeavyTripleDashed),
"HeavyTripleDashed"
);
assert_eq!(
format!("{}", BorderType::LightQuadrupleDashed),
"LightQuadrupleDashed"
);
assert_eq!(
format!("{}", BorderType::HeavyQuadrupleDashed),
"HeavyQuadrupleDashed"
);
}
#[test]
fn border_type_from_str() {
assert_eq!("Plain".parse(), Ok(BorderType::Plain));
assert_eq!("Rounded".parse(), Ok(BorderType::Rounded));
assert_eq!("Double".parse(), Ok(BorderType::Double));
assert_eq!("Thick".parse(), Ok(BorderType::Thick));
assert_eq!(
"LightDoubleDashed".parse(),
Ok(BorderType::LightDoubleDashed)
);
assert_eq!(
"HeavyDoubleDashed".parse(),
Ok(BorderType::HeavyDoubleDashed)
);
assert_eq!(
"LightTripleDashed".parse(),
Ok(BorderType::LightTripleDashed)
);
assert_eq!(
"HeavyTripleDashed".parse(),
Ok(BorderType::HeavyTripleDashed)
);
assert_eq!(
"LightQuadrupleDashed".parse(),
Ok(BorderType::LightQuadrupleDashed)
);
assert_eq!(
"HeavyQuadrupleDashed".parse(),
Ok(BorderType::HeavyQuadrupleDashed)
);
assert_eq!("".parse::<BorderType>(), Err(ParseError::VariantNotFound));
}
#[test]
fn render_plain_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Plain)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌────────┐",
"│ │",
"└────────┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_rounded_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Rounded)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"╭────────╮",
"│ │",
"╰────────╯",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_double_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Double)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"╔════════╗",
"║ ║",
"╚════════╝",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_quadrant_inside() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::QuadrantInside)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"▗▄▄▄▄▄▄▄▄▖",
"▐ ▌",
"▝▀▀▀▀▀▀▀▀▘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_border_quadrant_outside() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::QuadrantOutside)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"▛▀▀▀▀▀▀▀▀▜",
"▌ ▐",
"▙▄▄▄▄▄▄▄▄▟",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_solid_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::Thick)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┏━━━━━━━━┓",
"┃ ┃",
"┗━━━━━━━━┛",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_light_double_dashed_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::LightDoubleDashed)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌╌╌╌╌╌╌╌╌┐",
"╎ ╎",
"└╌╌╌╌╌╌╌╌┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_heavy_double_dashed_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::HeavyDoubleDashed)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┏╍╍╍╍╍╍╍╍┓",
"╏ ╏",
"┗╍╍╍╍╍╍╍╍┛",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_light_triple_dashed_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::LightTripleDashed)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌┄┄┄┄┄┄┄┄┐",
"┆ ┆",
"└┄┄┄┄┄┄┄┄┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_heavy_triple_dashed_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::HeavyTripleDashed)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┏┅┅┅┅┅┅┅┅┓",
"┇ ┇",
"┗┅┅┅┅┅┅┅┅┛",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_light_quadruple_dashed_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::LightQuadrupleDashed)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌┈┈┈┈┈┈┈┈┐",
"┊ ┊",
"└┈┈┈┈┈┈┈┈┘",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_heavy_quadruple_dashed_border() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_type(BorderType::HeavyQuadrupleDashed)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┏┉┉┉┉┉┉┉┉┓",
"┋ ┋",
"┗┉┉┉┉┉┉┉┉┛",
]);
assert_eq!(buffer, expected);
}
#[test]
fn render_custom_border_set() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::bordered()
.border_set(border::Set {
top_left: "1",
top_right: "2",
bottom_left: "3",
bottom_right: "4",
vertical_left: "L",
vertical_right: "R",
horizontal_top: "T",
horizontal_bottom: "B",
})
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"1TTTTTTTT2",
"L R",
"3BBBBBBBB4",
]);
assert_eq!(buffer, expected);
}
#[rstest]
#[case::replace(MergeStrategy::Replace)]
#[case::exact(MergeStrategy::Exact)]
#[case::fuzzy(MergeStrategy::Fuzzy)]
fn render_partial_borders(#[case] strategy: MergeStrategy) {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::new()
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
.merge_borders(strategy)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌────────┐",
"│ │",
"└────────┘",
]);
assert_eq!(buffer, expected);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::new()
.borders(Borders::TOP | Borders::LEFT)
.merge_borders(strategy)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"┌─────────",
"│ ",
"│ ",
]);
assert_eq!(buffer, expected);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::new()
.borders(Borders::TOP | Borders::RIGHT)
.merge_borders(strategy)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"─────────┐",
" │",
" │",
]);
assert_eq!(buffer, expected);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::new()
.borders(Borders::BOTTOM | Borders::LEFT)
.merge_borders(strategy)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"│ ",
"│ ",
"└─────────",
]);
assert_eq!(buffer, expected);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::new()
.borders(Borders::BOTTOM | Borders::RIGHT)
.merge_borders(strategy)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
" │",
" │",
"─────────┘",
]);
assert_eq!(buffer, expected);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::new()
.borders(Borders::TOP | Borders::BOTTOM)
.merge_borders(strategy)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"──────────",
" ",
"──────────",
]);
assert_eq!(buffer, expected);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
Block::new()
.borders(Borders::LEFT | Borders::RIGHT)
.merge_borders(strategy)
.render(buffer.area, &mut buffer);
#[rustfmt::skip]
let expected = Buffer::with_lines([
"│ │",
"│ │",
"│ │",
]);
assert_eq!(buffer, expected);
}
#[rstest]
#[case::replace(MergeStrategy::Replace, include_str!("../tests/block/merge_replace.txt"))]
#[case::exact(MergeStrategy::Exact, include_str!("../tests/block/merge_exact.txt"))]
#[case::fuzzy(MergeStrategy::Fuzzy, include_str!("../tests/block/merge_fuzzy.txt"))]
fn render_merged_borders(#[case] strategy: MergeStrategy, #[case] expected: &'static str) {
let border_types = [
BorderType::Plain,
BorderType::Rounded,
BorderType::Thick,
BorderType::Double,
BorderType::LightDoubleDashed,
BorderType::HeavyDoubleDashed,
BorderType::LightTripleDashed,
BorderType::HeavyTripleDashed,
BorderType::LightQuadrupleDashed,
BorderType::HeavyQuadrupleDashed,
];
let rects = [
(Rect::new(0, 0, 5, 5), Rect::new(4, 4, 5, 5)),
(Rect::new(10, 0, 5, 5), Rect::new(12, 2, 5, 5)),
(Rect::new(18, 0, 5, 5), Rect::new(22, 0, 5, 5)),
(Rect::new(28, 0, 5, 5), Rect::new(28, 4, 5, 5)),
];
let mut buffer = Buffer::empty(Rect::new(0, 0, 43, 1000));
let mut offset = Offset::ZERO;
for (border_type_1, border_type_2) in iproduct!(border_types, border_types) {
let title = format!("{border_type_1} + {border_type_2}");
let title_area = Rect::new(0, 0, 43, 1) + offset;
title.render(title_area, &mut buffer);
offset.y += 1;
for (rect_1, rect_2) in rects {
Block::bordered()
.border_type(border_type_1)
.merge_borders(strategy)
.render(rect_1 + offset, &mut buffer);
Block::bordered()
.border_type(border_type_2)
.merge_borders(strategy)
.render(rect_2 + offset, &mut buffer);
}
offset.y += 9;
}
pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
}
#[rstest]
#[case::replace(MergeStrategy::Replace, Buffer::with_lines([
"┏block top━━┓",
"┃ ┃",
"┗━━━━━━━━━━━┛",
"│ │",
"└───────────┘",
])
)]
#[case::replace(MergeStrategy::Exact, Buffer::with_lines([
"┏block top━━┓",
"┃ ┃",
"┡block btm━━┩",
"│ │",
"└───────────┘",
])
)]
#[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
"┏block top━━┓",
"┃ ┃",
"┡block btm━━┩",
"│ │",
"└───────────┘",
])
)]
fn merged_titles_bottom_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
Block::bordered()
.title("block btm")
.render(Rect::new(0, 2, 13, 3), &mut buffer);
Block::bordered()
.title("block top")
.border_type(BorderType::Thick)
.merge_borders(strategy)
.render(Rect::new(0, 0, 13, 3), &mut buffer);
assert_eq!(buffer, expected);
}
#[rstest]
#[case::replace(MergeStrategy::Replace, Buffer::with_lines([
"┏block top━━┓",
"┃ ┃",
"┌block btm──┐",
"│ │",
"└───────────┘",
])
)]
#[case::replace(MergeStrategy::Exact, Buffer::with_lines([
"┏block top━━┓",
"┃ ┃",
"┞block btm──┦",
"│ │",
"└───────────┘",
])
)]
#[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
"┏block top━━┓",
"┃ ┃",
"┞block btm──┦",
"│ │",
"└───────────┘",
])
)]
fn merged_titles_top_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
Block::bordered()
.title("block top")
.border_type(BorderType::Thick)
.render(Rect::new(0, 0, 13, 3), &mut buffer);
Block::bordered()
.title("block btm")
.merge_borders(strategy)
.render(Rect::new(0, 2, 13, 3), &mut buffer);
assert_eq!(buffer, expected);
}
#[test]
fn left_titles() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title("L12")
.title("L34")
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["L12 L34 "]));
}
#[test]
fn left_titles_truncated() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title("L12345")
.title("L67890")
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
}
#[test]
fn center_titles() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title(Line::from("C12").centered())
.title(Line::from("C34").centered())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([" C12 C34 "]));
}
#[test]
fn center_titles_truncated() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title(Line::from("C12345").centered())
.title(Line::from("C67890").centered())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["12345 C678"]));
}
#[test]
fn right_titles() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title(Line::from("R12").right_aligned())
.title(Line::from("R34").right_aligned())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([" R12 R34"]));
}
#[test]
fn right_titles_truncated() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title(Line::from("R12345").right_aligned())
.title(Line::from("R67890").right_aligned())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["345 R67890"]));
}
#[test]
fn center_title_truncates_left_title() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title("L1234")
.title(Line::from("C5678").centered())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["L1C5678 "]));
}
#[test]
fn right_title_truncates_left_title() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title("L12345")
.title(Line::from("R67890").right_aligned())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["L123R67890"]));
}
#[test]
fn right_title_truncates_center_title() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
Block::new()
.title(Line::from("C12345").centered())
.title(Line::from("R67890").right_aligned())
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines([" C1R67890"]));
}
#[test]
fn render_in_minimal_buffer() {
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
Block::bordered()
.title("I'm too big for this buffer")
.padding(Padding::uniform(10))
.render(buffer.area, &mut buffer);
assert_eq!(buffer, Buffer::with_lines(["┌"]));
}
#[test]
fn render_in_zero_size_buffer() {
let mut buffer = Buffer::empty(Rect::ZERO);
Block::bordered()
.title("I'm too big for this buffer")
.padding(Padding::uniform(10))
.render(buffer.area, &mut buffer);
}
}