1use alloc::vec::Vec;
9
10use itertools::Itertools;
11use ratatui_core::buffer::Buffer;
12use ratatui_core::layout::{Alignment, Rect};
13use ratatui_core::style::{Style, Styled};
14use ratatui_core::symbols::border;
15use ratatui_core::symbols::merge::MergeStrategy;
16use ratatui_core::text::Line;
17use ratatui_core::widgets::Widget;
18use strum::{Display, EnumString};
19
20pub use self::padding::Padding;
21use crate::borders::{BorderType, Borders};
22
23mod padding;
24
25#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
214pub struct Block<'a> {
215 titles: Vec<(Option<TitlePosition>, Line<'a>)>,
217 titles_style: Style,
219 titles_alignment: Alignment,
221 titles_position: TitlePosition,
223 borders: Borders,
225 border_style: Style,
227 border_set: border::Set<'a>,
230 style: Style,
232 padding: Padding,
234 merge_borders: MergeStrategy,
236}
237
238#[derive(Debug, Default, Display, EnumString, Clone, Copy, PartialEq, Eq, Hash)]
255pub enum TitlePosition {
256 #[default]
258 Top,
259 Bottom,
261}
262
263impl<'a> Block<'a> {
264 pub const fn new() -> Self {
266 Self {
267 titles: Vec::new(),
268 titles_style: Style::new(),
269 titles_alignment: Alignment::Left,
270 titles_position: TitlePosition::Top,
271 borders: Borders::NONE,
272 border_style: Style::new(),
273 border_set: BorderType::Plain.to_border_set(),
274 style: Style::new(),
275 padding: Padding::ZERO,
276 merge_borders: MergeStrategy::Replace,
277 }
278 }
279
280 pub const fn bordered() -> Self {
288 let mut block = Self::new();
289 block.borders = Borders::ALL;
290 block
291 }
292
293 #[must_use = "method moves the value of self and returns the modified value"]
360 pub fn title<T>(mut self, title: T) -> Self
361 where
362 T: Into<Line<'a>>,
363 {
364 self.titles.push((None, title.into()));
365 self
366 }
367
368 #[must_use = "method moves the value of self and returns the modified value"]
391 pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
392 let line = title.into();
393 self.titles.push((Some(TitlePosition::Top), line));
394 self
395 }
396
397 #[must_use = "method moves the value of self and returns the modified value"]
420 pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
421 let line = title.into();
422 self.titles.push((Some(TitlePosition::Bottom), line));
423 self
424 }
425
426 #[must_use = "method moves the value of self and returns the modified value"]
439 pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
440 self.titles_style = style.into();
441 self
442 }
443
444 #[must_use = "method moves the value of self and returns the modified value"]
465 pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
466 self.titles_alignment = alignment;
467 self
468 }
469
470 #[must_use = "method moves the value of self and returns the modified value"]
488 pub const fn title_position(mut self, position: TitlePosition) -> Self {
489 self.titles_position = position;
490 self
491 }
492
493 #[must_use = "method moves the value of self and returns the modified value"]
514 pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
515 self.border_style = style.into();
516 self
517 }
518
519 #[must_use = "method moves the value of self and returns the modified value"]
556 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
557 self.style = style.into();
558 self
559 }
560
561 #[must_use = "method moves the value of self and returns the modified value"]
575 pub const fn borders(mut self, flag: Borders) -> Self {
576 self.borders = flag;
577 self
578 }
579
580 #[must_use = "method moves the value of self and returns the modified value"]
600 pub const fn border_type(mut self, border_type: BorderType) -> Self {
601 self.border_set = border_type.to_border_set();
602 self
603 }
604
605 #[must_use = "method moves the value of self and returns the modified value"]
620 pub const fn border_set(mut self, border_set: border::Set<'a>) -> Self {
621 self.border_set = border_set;
622 self
623 }
624
625 #[must_use = "method moves the value of self and returns the modified value"]
654 pub const fn padding(mut self, padding: Padding) -> Self {
655 self.padding = padding;
656 self
657 }
658
659 #[must_use = "method moves the value of self and returns the modified value"]
701 pub const fn merge_borders(mut self, strategy: MergeStrategy) -> Self {
702 self.merge_borders = strategy;
703 self
704 }
705
706 pub fn inner(&self, area: Rect) -> Rect {
733 let mut inner = area;
734 if self.borders.intersects(Borders::LEFT) {
735 inner.x = inner.x.saturating_add(1).min(inner.right());
736 inner.width = inner.width.saturating_sub(1);
737 }
738 if self.borders.intersects(Borders::TOP) || self.has_title_at_position(TitlePosition::Top) {
739 inner.y = inner.y.saturating_add(1).min(inner.bottom());
740 inner.height = inner.height.saturating_sub(1);
741 }
742 if self.borders.intersects(Borders::RIGHT) {
743 inner.width = inner.width.saturating_sub(1);
744 }
745 if self.borders.intersects(Borders::BOTTOM)
746 || self.has_title_at_position(TitlePosition::Bottom)
747 {
748 inner.height = inner.height.saturating_sub(1);
749 }
750
751 inner.x = inner.x.saturating_add(self.padding.left);
752 inner.y = inner.y.saturating_add(self.padding.top);
753
754 inner.width = inner
755 .width
756 .saturating_sub(self.padding.left + self.padding.right);
757 inner.height = inner
758 .height
759 .saturating_sub(self.padding.top + self.padding.bottom);
760
761 inner
762 }
763
764 fn has_title_at_position(&self, position: TitlePosition) -> bool {
765 self.titles
766 .iter()
767 .any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
768 }
769}
770
771impl Widget for Block<'_> {
772 fn render(self, area: Rect, buf: &mut Buffer) {
773 Widget::render(&self, area, buf);
774 }
775}
776
777impl Widget for &Block<'_> {
778 fn render(self, area: Rect, buf: &mut Buffer) {
779 let area = area.intersection(buf.area);
780 if area.is_empty() {
781 return;
782 }
783 buf.set_style(area, self.style);
784 self.render_borders(area, buf);
785 self.render_titles(area, buf);
786 }
787}
788
789impl Block<'_> {
790 fn render_borders(&self, area: Rect, buf: &mut Buffer) {
791 self.render_sides(area, buf);
792 self.render_corners(area, buf);
793 }
794
795 fn render_sides(&self, area: Rect, buf: &mut Buffer) {
796 let left = area.left();
797 let top = area.top();
798 let right = area.right() - 1;
800 let bottom = area.bottom() - 1;
801
802 let is_replace = self.merge_borders != MergeStrategy::Replace;
807 let left_inset = left + u16::from(is_replace && self.borders.contains(Borders::LEFT));
808 let top_inset = top + u16::from(is_replace && self.borders.contains(Borders::TOP));
809 let right_inset = right - u16::from(is_replace && self.borders.contains(Borders::RIGHT));
810 let bottom_inset = bottom - u16::from(is_replace && self.borders.contains(Borders::BOTTOM));
811
812 let sides = [
813 (
814 Borders::LEFT,
815 left..=left,
816 top_inset..=bottom_inset,
817 self.border_set.vertical_left,
818 ),
819 (
820 Borders::TOP,
821 left_inset..=right_inset,
822 top..=top,
823 self.border_set.horizontal_top,
824 ),
825 (
826 Borders::RIGHT,
827 right..=right,
828 top_inset..=bottom_inset,
829 self.border_set.vertical_right,
830 ),
831 (
832 Borders::BOTTOM,
833 left_inset..=right_inset,
834 bottom..=bottom,
835 self.border_set.horizontal_bottom,
836 ),
837 ];
838 for (border, x_range, y_range, symbol) in sides {
839 if self.borders.contains(border) {
840 for x in x_range {
841 for y in y_range.clone() {
842 buf[(x, y)]
843 .merge_symbol(symbol, self.merge_borders)
844 .set_style(self.border_style);
845 }
846 }
847 }
848 }
849 }
850
851 fn render_corners(&self, area: Rect, buf: &mut Buffer) {
852 let corners = [
853 (
854 Borders::RIGHT | Borders::BOTTOM,
855 area.right() - 1,
856 area.bottom() - 1,
857 self.border_set.bottom_right,
858 ),
859 (
860 Borders::RIGHT | Borders::TOP,
861 area.right() - 1,
862 area.top(),
863 self.border_set.top_right,
864 ),
865 (
866 Borders::LEFT | Borders::BOTTOM,
867 area.left(),
868 area.bottom() - 1,
869 self.border_set.bottom_left,
870 ),
871 (
872 Borders::LEFT | Borders::TOP,
873 area.left(),
874 area.top(),
875 self.border_set.top_left,
876 ),
877 ];
878
879 for (border, x, y, symbol) in corners {
880 if self.borders.contains(border) {
881 buf[(x, y)]
882 .merge_symbol(symbol, self.merge_borders)
883 .set_style(self.border_style);
884 }
885 }
886 }
887 fn render_titles(&self, area: Rect, buf: &mut Buffer) {
888 self.render_title_position(TitlePosition::Top, area, buf);
889 self.render_title_position(TitlePosition::Bottom, area, buf);
890 }
891
892 fn render_title_position(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
893 self.render_left_titles(position, area, buf);
895 self.render_center_titles(position, area, buf);
896 self.render_right_titles(position, area, buf);
897 }
898
899 #[expect(clippy::similar_names)]
906 fn render_right_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
907 let titles = self.filtered_titles(position, Alignment::Right);
908 let mut titles_area = self.titles_area(area, position);
909
910 for title in titles.rev() {
912 if titles_area.is_empty() {
913 break;
914 }
915 let title_width = title.width() as u16;
916 let title_area = Rect {
917 x: titles_area
918 .right()
919 .saturating_sub(title_width)
920 .max(titles_area.left()),
921 width: title_width.min(titles_area.width),
922 ..titles_area
923 };
924 buf.set_style(title_area, self.titles_style);
925 title.render(title_area, buf);
926
927 titles_area.width = titles_area
929 .width
930 .saturating_sub(title_width)
931 .saturating_sub(1); }
933 }
934
935 fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
937 let area = self.titles_area(area, position);
938 let titles = self
939 .filtered_titles(position, Alignment::Center)
940 .collect_vec();
941 let total_width = titles
943 .iter()
944 .map(|title| title.width() as u16 + 1)
945 .sum::<u16>()
946 .saturating_sub(1);
947
948 if total_width <= area.width {
949 self.render_centered_titles_without_truncation(titles, total_width, area, buf);
950 } else {
951 self.render_centered_titles_with_truncation(titles, total_width, area, buf);
952 }
953 }
954
955 fn render_centered_titles_without_truncation(
956 &self,
957 titles: Vec<&Line<'_>>,
958 total_width: u16,
959 area: Rect,
960 buf: &mut Buffer,
961 ) {
962 let x = area.left() + area.width.saturating_sub(total_width) / 2;
964 let mut area = Rect { x, ..area };
965 for title in titles {
966 let width = title.width() as u16;
967 let title_area = Rect { width, ..area };
968 buf.set_style(title_area, self.titles_style);
969 title.render(title_area, buf);
970 area.x = area.x.saturating_add(width + 1);
972 area.width = area.width.saturating_sub(width + 1);
973 }
974 }
975
976 fn render_centered_titles_with_truncation(
977 &self,
978 titles: Vec<&Line<'_>>,
979 total_width: u16,
980 mut area: Rect,
981 buf: &mut Buffer,
982 ) {
983 let mut offset = total_width.saturating_sub(area.width) / 2;
986 for title in titles {
987 if area.is_empty() {
988 break;
989 }
990 let width = area.width.min(title.width() as u16).saturating_sub(offset);
991 let title_area = Rect { width, ..area };
992 buf.set_style(title_area, self.titles_style);
993 if offset > 0 {
994 title.clone().right_aligned().render(title_area, buf);
996 offset = offset.saturating_sub(width).saturating_sub(1);
997 } else {
998 title.clone().left_aligned().render(title_area, buf);
1000 }
1001 area.x = area.x.saturating_add(width + 1);
1003 area.width = area.width.saturating_sub(width + 1);
1004 }
1005 }
1006
1007 #[expect(clippy::similar_names)]
1009 fn render_left_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
1010 let titles = self.filtered_titles(position, Alignment::Left);
1011 let mut titles_area = self.titles_area(area, position);
1012 for title in titles {
1013 if titles_area.is_empty() {
1014 break;
1015 }
1016 let title_width = title.width() as u16;
1017 let title_area = Rect {
1018 width: title_width.min(titles_area.width),
1019 ..titles_area
1020 };
1021 buf.set_style(title_area, self.titles_style);
1022 title.render(title_area, buf);
1023
1024 titles_area.x = titles_area.x.saturating_add(title_width + 1);
1026 titles_area.width = titles_area.width.saturating_sub(title_width + 1);
1027 }
1028 }
1029
1030 fn filtered_titles(
1032 &self,
1033 position: TitlePosition,
1034 alignment: Alignment,
1035 ) -> impl DoubleEndedIterator<Item = &Line<'_>> {
1036 self.titles
1037 .iter()
1038 .filter(move |(pos, _)| pos.unwrap_or(self.titles_position) == position)
1039 .filter(move |(_, line)| line.alignment.unwrap_or(self.titles_alignment) == alignment)
1040 .map(|(_, line)| line)
1041 }
1042
1043 fn titles_area(&self, area: Rect, position: TitlePosition) -> Rect {
1046 let left_border = u16::from(self.borders.contains(Borders::LEFT));
1047 let right_border = u16::from(self.borders.contains(Borders::RIGHT));
1048 Rect {
1049 x: area.left() + left_border,
1050 y: match position {
1051 TitlePosition::Top => area.top(),
1052 TitlePosition::Bottom => area.bottom() - 1,
1053 },
1054 width: area
1055 .width
1056 .saturating_sub(left_border)
1057 .saturating_sub(right_border),
1058 height: 1,
1059 }
1060 }
1061
1062 pub(crate) fn horizontal_space(&self) -> (u16, u16) {
1066 let left = self
1067 .padding
1068 .left
1069 .saturating_add(u16::from(self.borders.contains(Borders::LEFT)));
1070 let right = self
1071 .padding
1072 .right
1073 .saturating_add(u16::from(self.borders.contains(Borders::RIGHT)));
1074 (left, right)
1075 }
1076
1077 pub(crate) fn vertical_space(&self) -> (u16, u16) {
1082 let has_top =
1083 self.borders.contains(Borders::TOP) || self.has_title_at_position(TitlePosition::Top);
1084 let top = self.padding.top + u16::from(has_top);
1085 let has_bottom = self.borders.contains(Borders::BOTTOM)
1086 || self.has_title_at_position(TitlePosition::Bottom);
1087 let bottom = self.padding.bottom + u16::from(has_bottom);
1088 (top, bottom)
1089 }
1090}
1091
1092pub trait BlockExt {
1097 fn inner_if_some(&self, area: Rect) -> Rect;
1101}
1102
1103impl BlockExt for Option<Block<'_>> {
1104 fn inner_if_some(&self, area: Rect) -> Rect {
1105 self.as_ref().map_or(area, |block| block.inner(area))
1106 }
1107}
1108
1109impl Styled for Block<'_> {
1110 type Item = Self;
1111
1112 fn style(&self) -> Style {
1113 self.style
1114 }
1115
1116 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
1117 self.style(style)
1118 }
1119}
1120
1121#[cfg(test)]
1122mod tests {
1123 use alloc::{format, vec};
1124
1125 use itertools::iproduct;
1126 use ratatui_core::layout::Offset;
1127 use ratatui_core::style::{Color, Modifier, Stylize};
1128 use rstest::rstest;
1129 use strum::ParseError;
1130
1131 use super::*;
1132
1133 #[test]
1134 fn create_with_all_borders() {
1135 let block = Block::bordered();
1136 assert_eq!(block.borders, Borders::all());
1137 }
1138
1139 #[rstest]
1140 #[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
1141 #[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
1142 #[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
1143 #[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1144 #[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
1145 #[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
1146 #[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
1147 #[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1148 #[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
1149 #[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
1150 #[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
1151 #[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1152 #[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
1153 #[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
1154 #[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
1155 #[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1156 #[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
1157 #[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
1158 #[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
1159 #[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
1160 #[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
1161 #[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
1162 fn inner_takes_into_account_the_borders(
1163 #[case] borders: Borders,
1164 #[case] area: Rect,
1165 #[case] expected: Rect,
1166 ) {
1167 let block = Block::new().borders(borders);
1168 assert_eq!(block.inner(area), expected);
1169 }
1170
1171 #[rstest]
1172 #[case::left(Alignment::Left)]
1173 #[case::center(Alignment::Center)]
1174 #[case::right(Alignment::Right)]
1175 fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
1176 let area = Rect::new(0, 0, 0, 1);
1177 let expected = Rect::new(0, 1, 0, 0);
1178
1179 let block = Block::new().title(Line::from("Test").alignment(alignment));
1180 assert_eq!(block.inner(area), expected);
1181 }
1182
1183 #[rstest]
1184 #[case::top_top(Block::new().title_top("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 1))]
1185 #[case::top_bot(Block::new().title_top("Test").borders(Borders::BOTTOM), Rect::new(0, 1, 0, 0))]
1186 #[case::bot_top(Block::new().title_bottom("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 0))]
1187 #[case::bot_bot(Block::new().title_bottom("Test").borders(Borders::BOTTOM), Rect::new(0, 0, 0, 1))]
1188 fn inner_takes_into_account_border_and_title(#[case] block: Block, #[case] expected: Rect) {
1189 let area = Rect::new(0, 0, 0, 2);
1190 assert_eq!(block.inner(area), expected);
1191 }
1192
1193 #[test]
1194 fn has_title_at_position_takes_into_account_all_positioning_declarations() {
1195 let block = Block::new();
1196 assert!(!block.has_title_at_position(TitlePosition::Top));
1197 assert!(!block.has_title_at_position(TitlePosition::Bottom));
1198
1199 let block = Block::new().title_top("test");
1200 assert!(block.has_title_at_position(TitlePosition::Top));
1201 assert!(!block.has_title_at_position(TitlePosition::Bottom));
1202
1203 let block = Block::new().title_bottom("test");
1204 assert!(!block.has_title_at_position(TitlePosition::Top));
1205 assert!(block.has_title_at_position(TitlePosition::Bottom));
1206
1207 let block = Block::new().title_top("test").title_bottom("test");
1208 assert!(block.has_title_at_position(TitlePosition::Top));
1209 assert!(block.has_title_at_position(TitlePosition::Bottom));
1210 }
1211
1212 #[rstest]
1213 #[case::none(Borders::NONE, (0, 0))]
1214 #[case::top(Borders::TOP, (1, 0))]
1215 #[case::right(Borders::RIGHT, (0, 0))]
1216 #[case::bottom(Borders::BOTTOM, (0, 1))]
1217 #[case::left(Borders::LEFT, (0, 0))]
1218 #[case::top_right(Borders::TOP | Borders::RIGHT, (1, 0))]
1219 #[case::top_bottom(Borders::TOP | Borders::BOTTOM, (1, 1))]
1220 #[case::top_left(Borders::TOP | Borders::LEFT, (1, 0))]
1221 #[case::bottom_right(Borders::BOTTOM | Borders::RIGHT, (0, 1))]
1222 #[case::bottom_left(Borders::BOTTOM | Borders::LEFT, (0, 1))]
1223 #[case::left_right(Borders::LEFT | Borders::RIGHT, (0, 0))]
1224 fn vertical_space_takes_into_account_borders(
1225 #[case] borders: Borders,
1226 #[case] vertical_space: (u16, u16),
1227 ) {
1228 let block = Block::new().borders(borders);
1229 assert_eq!(block.vertical_space(), vertical_space);
1230 }
1231
1232 #[rstest]
1233 #[case::top_border_top_p1(Borders::TOP, Padding::new(0, 0, 1, 0), (2, 0))]
1234 #[case::right_border_top_p1(Borders::RIGHT, Padding::new(0, 0, 1, 0), (1, 0))]
1235 #[case::bottom_border_top_p1(Borders::BOTTOM, Padding::new(0, 0, 1, 0), (1, 1))]
1236 #[case::left_border_top_p1(Borders::LEFT, Padding::new(0, 0, 1, 0), (1, 0))]
1237 #[case::top_bottom_border_all_p3(Borders::TOP | Borders::BOTTOM, Padding::new(100, 100, 4, 5), (5, 6))]
1238 #[case::no_border(Borders::NONE, Padding::new(100, 100, 10, 13), (10, 13))]
1239 #[case::all(Borders::ALL, Padding::new(100, 100, 1, 3), (2, 4))]
1240 fn vertical_space_takes_into_account_padding(
1241 #[case] borders: Borders,
1242 #[case] padding: Padding,
1243 #[case] vertical_space: (u16, u16),
1244 ) {
1245 let block = Block::new().borders(borders).padding(padding);
1246 assert_eq!(block.vertical_space(), vertical_space);
1247 }
1248
1249 #[test]
1250 fn vertical_space_takes_into_account_titles() {
1251 let block = Block::new().title_top("Test");
1252 assert_eq!(block.vertical_space(), (1, 0));
1253
1254 let block = Block::new().title_bottom("Test");
1255 assert_eq!(block.vertical_space(), (0, 1));
1256 }
1257
1258 #[rstest]
1259 #[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Top, (1, 0))]
1260 #[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Top, (1, 0))]
1261 #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Top, (1, 1))]
1262 #[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Top, (1, 0))]
1263 #[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Bottom, (1, 1))]
1264 #[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Bottom, (0, 1))]
1265 #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Bottom, (0, 1))]
1266 #[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Bottom, (0, 1))]
1267 fn vertical_space_takes_into_account_borders_and_title(
1268 #[case] block: Block,
1269 #[case] borders: Borders,
1270 #[case] pos: TitlePosition,
1271 #[case] vertical_space: (u16, u16),
1272 ) {
1273 let block = block.borders(borders).title_position(pos).title("Test");
1274 assert_eq!(block.vertical_space(), vertical_space);
1275 }
1276
1277 #[test]
1278 fn horizontal_space_takes_into_account_borders() {
1279 let block = Block::bordered();
1280 assert_eq!(block.horizontal_space(), (1, 1));
1281
1282 let block = Block::new().borders(Borders::LEFT);
1283 assert_eq!(block.horizontal_space(), (1, 0));
1284
1285 let block = Block::new().borders(Borders::RIGHT);
1286 assert_eq!(block.horizontal_space(), (0, 1));
1287 }
1288
1289 #[test]
1290 fn horizontal_space_takes_into_account_padding() {
1291 let block = Block::new().padding(Padding::new(1, 1, 100, 100));
1292 assert_eq!(block.horizontal_space(), (1, 1));
1293
1294 let block = Block::new().padding(Padding::new(3, 5, 0, 0));
1295 assert_eq!(block.horizontal_space(), (3, 5));
1296
1297 let block = Block::new().padding(Padding::new(0, 1, 100, 100));
1298 assert_eq!(block.horizontal_space(), (0, 1));
1299
1300 let block = Block::new().padding(Padding::new(1, 0, 100, 100));
1301 assert_eq!(block.horizontal_space(), (1, 0));
1302 }
1303
1304 #[rstest]
1305 #[case::all_bordered_all_padded(Block::bordered(), Padding::new(1, 1, 1, 1), (2, 2))]
1306 #[case::all_bordered_left_padded(Block::bordered(), Padding::new(1, 0, 0, 0), (2, 1))]
1307 #[case::all_bordered_right_padded(Block::bordered(), Padding::new(0, 1, 0, 0), (1, 2))]
1308 #[case::all_bordered_top_padded(Block::bordered(), Padding::new(0, 0, 1, 0), (1, 1))]
1309 #[case::all_bordered_bottom_padded(Block::bordered(), Padding::new(0, 0, 0, 1), (1, 1))]
1310 #[case::left_bordered_left_padded(Block::new().borders(Borders::LEFT), Padding::new(1, 0, 0, 0), (2, 0))]
1311 #[case::left_bordered_right_padded(Block::new().borders(Borders::LEFT), Padding::new(0, 1, 0, 0), (1, 1))]
1312 #[case::right_bordered_right_padded(Block::new().borders(Borders::RIGHT), Padding::new(0, 1, 0, 0), (0, 2))]
1313 #[case::right_bordered_left_padded(Block::new().borders(Borders::RIGHT), Padding::new(1, 0, 0, 0), (1, 1))]
1314 fn horizontal_space_takes_into_account_borders_and_padding(
1315 #[case] block: Block,
1316 #[case] padding: Padding,
1317 #[case] horizontal_space: (u16, u16),
1318 ) {
1319 let block = block.padding(padding);
1320 assert_eq!(block.horizontal_space(), horizontal_space);
1321 }
1322
1323 #[test]
1324 const fn border_type_can_be_const() {
1325 const _PLAIN: border::Set = BorderType::border_symbols(BorderType::Plain);
1326 }
1327
1328 #[test]
1329 fn block_new() {
1330 assert_eq!(
1331 Block::new(),
1332 Block {
1333 titles: Vec::new(),
1334 titles_style: Style::new(),
1335 titles_alignment: Alignment::Left,
1336 titles_position: TitlePosition::Top,
1337 borders: Borders::NONE,
1338 border_style: Style::new(),
1339 border_set: BorderType::Plain.to_border_set(),
1340 style: Style::new(),
1341 padding: Padding::ZERO,
1342 merge_borders: MergeStrategy::Replace,
1343 }
1344 );
1345 }
1346
1347 #[test]
1348 const fn block_can_be_const() {
1349 const _DEFAULT_STYLE: Style = Style::new();
1350 const _DEFAULT_PADDING: Padding = Padding::uniform(1);
1351 const _DEFAULT_BLOCK: Block = Block::bordered()
1352 .title_alignment(Alignment::Left)
1357 .title_position(TitlePosition::Top)
1358 .padding(_DEFAULT_PADDING);
1359 }
1360
1361 #[test]
1363 fn style_into_works_from_user_view() {
1364 let block = Block::new().style(Style::new().red());
1366 assert_eq!(block.style, Style::new().red());
1367
1368 let block = Block::new().style(Color::Red);
1370 assert_eq!(block.style, Style::new().red());
1371
1372 let block = Block::new().style((Color::Red, Color::Blue));
1374 assert_eq!(block.style, Style::new().red().on_blue());
1375
1376 let block = Block::new().style(Modifier::BOLD | Modifier::ITALIC);
1378 assert_eq!(block.style, Style::new().bold().italic());
1379
1380 let block = Block::new().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
1382 assert_eq!(block.style, Style::new().bold().italic().not_dim());
1383
1384 let block = Block::new().style((Color::Red, Modifier::BOLD));
1386 assert_eq!(block.style, Style::new().red().bold());
1387
1388 let block = Block::new().style((Color::Red, Color::Blue, Modifier::BOLD));
1390 assert_eq!(block.style, Style::new().red().on_blue().bold());
1391
1392 let block = Block::new().style((
1394 Color::Red,
1395 Color::Blue,
1396 Modifier::BOLD | Modifier::ITALIC,
1397 Modifier::DIM,
1398 ));
1399 assert_eq!(
1400 block.style,
1401 Style::new().red().on_blue().bold().italic().not_dim()
1402 );
1403 }
1404
1405 #[test]
1406 fn can_be_stylized() {
1407 let block = Block::new().black().on_white().bold().not_dim();
1408 assert_eq!(
1409 block.style,
1410 Style::default()
1411 .fg(Color::Black)
1412 .bg(Color::White)
1413 .add_modifier(Modifier::BOLD)
1414 .remove_modifier(Modifier::DIM)
1415 );
1416 }
1417
1418 #[test]
1419 fn title_top_bottom() {
1420 let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
1421 Block::bordered()
1422 .title_top(Line::raw("A").left_aligned())
1423 .title_top(Line::raw("B").centered())
1424 .title_top(Line::raw("C").right_aligned())
1425 .title_bottom(Line::raw("D").left_aligned())
1426 .title_bottom(Line::raw("E").centered())
1427 .title_bottom(Line::raw("F").right_aligned())
1428 .render(buffer.area, &mut buffer);
1429 #[rustfmt::skip]
1430 let expected = Buffer::with_lines([
1431 "┌A───B───C┐",
1432 "│ │",
1433 "└D───E───F┘",
1434 ]);
1435 assert_eq!(buffer, expected);
1436 }
1437
1438 #[test]
1439 fn title_alignment() {
1440 let tests = vec![
1441 (Alignment::Left, "test "),
1442 (Alignment::Center, " test "),
1443 (Alignment::Right, " test"),
1444 ];
1445 for (alignment, expected) in tests {
1446 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1447 Block::new()
1448 .title_alignment(alignment)
1449 .title("test")
1450 .render(buffer.area, &mut buffer);
1451 assert_eq!(buffer, Buffer::with_lines([expected]));
1452 }
1453 }
1454
1455 #[test]
1456 fn title_alignment_overrides_block_title_alignment() {
1457 let tests = vec![
1458 (Alignment::Right, Alignment::Left, "test "),
1459 (Alignment::Left, Alignment::Center, " test "),
1460 (Alignment::Center, Alignment::Right, " test"),
1461 ];
1462 for (block_title_alignment, alignment, expected) in tests {
1463 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1464 Block::new()
1465 .title_alignment(block_title_alignment)
1466 .title(Line::from("test").alignment(alignment))
1467 .render(buffer.area, &mut buffer);
1468 assert_eq!(buffer, Buffer::with_lines([expected]));
1469 }
1470 }
1471
1472 #[test]
1474 fn render_right_aligned_empty_title() {
1475 let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
1476 Block::new()
1477 .title_alignment(Alignment::Right)
1478 .title("")
1479 .render(buffer.area, &mut buffer);
1480 assert_eq!(buffer, Buffer::with_lines([" "; 3]));
1481 }
1482
1483 #[test]
1484 fn title_position() {
1485 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
1486 Block::new()
1487 .title_position(TitlePosition::Bottom)
1488 .title("test")
1489 .render(buffer.area, &mut buffer);
1490 assert_eq!(buffer, Buffer::with_lines([" ", "test"]));
1491 }
1492
1493 #[test]
1494 fn title_content_style() {
1495 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1496 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1497 Block::new()
1498 .title_alignment(alignment)
1499 .title("test".yellow())
1500 .render(buffer.area, &mut buffer);
1501 assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1502 }
1503 }
1504
1505 #[test]
1506 fn block_title_style() {
1507 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1508 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1509 Block::new()
1510 .title_alignment(alignment)
1511 .title_style(Style::new().yellow())
1512 .title("test")
1513 .render(buffer.area, &mut buffer);
1514 assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1515 }
1516 }
1517
1518 #[test]
1519 fn title_style_overrides_block_title_style() {
1520 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1521 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1522 Block::new()
1523 .title_alignment(alignment)
1524 .title_style(Style::new().green().on_red())
1525 .title("test".yellow())
1526 .render(buffer.area, &mut buffer);
1527 assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
1528 }
1529 }
1530
1531 #[test]
1532 fn title_border_style() {
1533 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1534 Block::bordered()
1535 .title("test")
1536 .border_style(Style::new().yellow())
1537 .render(buffer.area, &mut buffer);
1538 #[rustfmt::skip]
1539 let mut expected = Buffer::with_lines([
1540 "┌test────┐",
1541 "│ │",
1542 "└────────┘",
1543 ]);
1544 expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
1545 expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
1546 assert_eq!(buffer, expected);
1547 }
1548
1549 #[test]
1550 fn border_type_to_string() {
1551 assert_eq!(format!("{}", BorderType::Plain), "Plain");
1552 assert_eq!(format!("{}", BorderType::Rounded), "Rounded");
1553 assert_eq!(format!("{}", BorderType::Double), "Double");
1554 assert_eq!(format!("{}", BorderType::Thick), "Thick");
1555 assert_eq!(
1556 format!("{}", BorderType::LightDoubleDashed),
1557 "LightDoubleDashed"
1558 );
1559 assert_eq!(
1560 format!("{}", BorderType::HeavyDoubleDashed),
1561 "HeavyDoubleDashed"
1562 );
1563 assert_eq!(
1564 format!("{}", BorderType::LightTripleDashed),
1565 "LightTripleDashed"
1566 );
1567 assert_eq!(
1568 format!("{}", BorderType::HeavyTripleDashed),
1569 "HeavyTripleDashed"
1570 );
1571 assert_eq!(
1572 format!("{}", BorderType::LightQuadrupleDashed),
1573 "LightQuadrupleDashed"
1574 );
1575 assert_eq!(
1576 format!("{}", BorderType::HeavyQuadrupleDashed),
1577 "HeavyQuadrupleDashed"
1578 );
1579 }
1580
1581 #[test]
1582 fn border_type_from_str() {
1583 assert_eq!("Plain".parse(), Ok(BorderType::Plain));
1584 assert_eq!("Rounded".parse(), Ok(BorderType::Rounded));
1585 assert_eq!("Double".parse(), Ok(BorderType::Double));
1586 assert_eq!("Thick".parse(), Ok(BorderType::Thick));
1587 assert_eq!(
1588 "LightDoubleDashed".parse(),
1589 Ok(BorderType::LightDoubleDashed)
1590 );
1591 assert_eq!(
1592 "HeavyDoubleDashed".parse(),
1593 Ok(BorderType::HeavyDoubleDashed)
1594 );
1595 assert_eq!(
1596 "LightTripleDashed".parse(),
1597 Ok(BorderType::LightTripleDashed)
1598 );
1599 assert_eq!(
1600 "HeavyTripleDashed".parse(),
1601 Ok(BorderType::HeavyTripleDashed)
1602 );
1603 assert_eq!(
1604 "LightQuadrupleDashed".parse(),
1605 Ok(BorderType::LightQuadrupleDashed)
1606 );
1607 assert_eq!(
1608 "HeavyQuadrupleDashed".parse(),
1609 Ok(BorderType::HeavyQuadrupleDashed)
1610 );
1611 assert_eq!("".parse::<BorderType>(), Err(ParseError::VariantNotFound));
1612 }
1613
1614 #[test]
1615 fn render_plain_border() {
1616 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1617 Block::bordered()
1618 .border_type(BorderType::Plain)
1619 .render(buffer.area, &mut buffer);
1620 #[rustfmt::skip]
1621 let expected = Buffer::with_lines([
1622 "┌────────┐",
1623 "│ │",
1624 "└────────┘",
1625 ]);
1626 assert_eq!(buffer, expected);
1627 }
1628
1629 #[test]
1630 fn render_rounded_border() {
1631 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1632 Block::bordered()
1633 .border_type(BorderType::Rounded)
1634 .render(buffer.area, &mut buffer);
1635 #[rustfmt::skip]
1636 let expected = Buffer::with_lines([
1637 "╭────────╮",
1638 "│ │",
1639 "╰────────╯",
1640 ]);
1641 assert_eq!(buffer, expected);
1642 }
1643
1644 #[test]
1645 fn render_double_border() {
1646 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1647 Block::bordered()
1648 .border_type(BorderType::Double)
1649 .render(buffer.area, &mut buffer);
1650 #[rustfmt::skip]
1651 let expected = Buffer::with_lines([
1652 "╔════════╗",
1653 "║ ║",
1654 "╚════════╝",
1655 ]);
1656 assert_eq!(buffer, expected);
1657 }
1658
1659 #[test]
1660 fn render_quadrant_inside() {
1661 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1662 Block::bordered()
1663 .border_type(BorderType::QuadrantInside)
1664 .render(buffer.area, &mut buffer);
1665 #[rustfmt::skip]
1666 let expected = Buffer::with_lines([
1667 "▗▄▄▄▄▄▄▄▄▖",
1668 "▐ ▌",
1669 "▝▀▀▀▀▀▀▀▀▘",
1670 ]);
1671 assert_eq!(buffer, expected);
1672 }
1673
1674 #[test]
1675 fn render_border_quadrant_outside() {
1676 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1677 Block::bordered()
1678 .border_type(BorderType::QuadrantOutside)
1679 .render(buffer.area, &mut buffer);
1680 #[rustfmt::skip]
1681 let expected = Buffer::with_lines([
1682 "▛▀▀▀▀▀▀▀▀▜",
1683 "▌ ▐",
1684 "▙▄▄▄▄▄▄▄▄▟",
1685 ]);
1686 assert_eq!(buffer, expected);
1687 }
1688
1689 #[test]
1690 fn render_solid_border() {
1691 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1692 Block::bordered()
1693 .border_type(BorderType::Thick)
1694 .render(buffer.area, &mut buffer);
1695 #[rustfmt::skip]
1696 let expected = Buffer::with_lines([
1697 "┏━━━━━━━━┓",
1698 "┃ ┃",
1699 "┗━━━━━━━━┛",
1700 ]);
1701 assert_eq!(buffer, expected);
1702 }
1703
1704 #[test]
1705 fn render_light_double_dashed_border() {
1706 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1707 Block::bordered()
1708 .border_type(BorderType::LightDoubleDashed)
1709 .render(buffer.area, &mut buffer);
1710 #[rustfmt::skip]
1711 let expected = Buffer::with_lines([
1712 "┌╌╌╌╌╌╌╌╌┐",
1713 "╎ ╎",
1714 "└╌╌╌╌╌╌╌╌┘",
1715 ]);
1716 assert_eq!(buffer, expected);
1717 }
1718
1719 #[test]
1720 fn render_heavy_double_dashed_border() {
1721 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1722 Block::bordered()
1723 .border_type(BorderType::HeavyDoubleDashed)
1724 .render(buffer.area, &mut buffer);
1725 #[rustfmt::skip]
1726 let expected = Buffer::with_lines([
1727 "┏╍╍╍╍╍╍╍╍┓",
1728 "╏ ╏",
1729 "┗╍╍╍╍╍╍╍╍┛",
1730 ]);
1731 assert_eq!(buffer, expected);
1732 }
1733
1734 #[test]
1735 fn render_light_triple_dashed_border() {
1736 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1737 Block::bordered()
1738 .border_type(BorderType::LightTripleDashed)
1739 .render(buffer.area, &mut buffer);
1740 #[rustfmt::skip]
1741 let expected = Buffer::with_lines([
1742 "┌┄┄┄┄┄┄┄┄┐",
1743 "┆ ┆",
1744 "└┄┄┄┄┄┄┄┄┘",
1745 ]);
1746 assert_eq!(buffer, expected);
1747 }
1748
1749 #[test]
1750 fn render_heavy_triple_dashed_border() {
1751 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1752 Block::bordered()
1753 .border_type(BorderType::HeavyTripleDashed)
1754 .render(buffer.area, &mut buffer);
1755 #[rustfmt::skip]
1756 let expected = Buffer::with_lines([
1757 "┏┅┅┅┅┅┅┅┅┓",
1758 "┇ ┇",
1759 "┗┅┅┅┅┅┅┅┅┛",
1760 ]);
1761 assert_eq!(buffer, expected);
1762 }
1763
1764 #[test]
1765 fn render_light_quadruple_dashed_border() {
1766 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1767 Block::bordered()
1768 .border_type(BorderType::LightQuadrupleDashed)
1769 .render(buffer.area, &mut buffer);
1770 #[rustfmt::skip]
1771 let expected = Buffer::with_lines([
1772 "┌┈┈┈┈┈┈┈┈┐",
1773 "┊ ┊",
1774 "└┈┈┈┈┈┈┈┈┘",
1775 ]);
1776 assert_eq!(buffer, expected);
1777 }
1778
1779 #[test]
1780 fn render_heavy_quadruple_dashed_border() {
1781 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1782 Block::bordered()
1783 .border_type(BorderType::HeavyQuadrupleDashed)
1784 .render(buffer.area, &mut buffer);
1785 #[rustfmt::skip]
1786 let expected = Buffer::with_lines([
1787 "┏┉┉┉┉┉┉┉┉┓",
1788 "┋ ┋",
1789 "┗┉┉┉┉┉┉┉┉┛",
1790 ]);
1791 assert_eq!(buffer, expected);
1792 }
1793
1794 #[test]
1795 fn render_custom_border_set() {
1796 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1797 Block::bordered()
1798 .border_set(border::Set {
1799 top_left: "1",
1800 top_right: "2",
1801 bottom_left: "3",
1802 bottom_right: "4",
1803 vertical_left: "L",
1804 vertical_right: "R",
1805 horizontal_top: "T",
1806 horizontal_bottom: "B",
1807 })
1808 .render(buffer.area, &mut buffer);
1809 #[rustfmt::skip]
1810 let expected = Buffer::with_lines([
1811 "1TTTTTTTT2",
1812 "L R",
1813 "3BBBBBBBB4",
1814 ]);
1815 assert_eq!(buffer, expected);
1816 }
1817
1818 #[rstest]
1819 #[case::replace(MergeStrategy::Replace)]
1820 #[case::exact(MergeStrategy::Exact)]
1821 #[case::fuzzy(MergeStrategy::Fuzzy)]
1822 fn render_partial_borders(#[case] strategy: MergeStrategy) {
1823 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1824 Block::new()
1825 .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
1826 .merge_borders(strategy)
1827 .render(buffer.area, &mut buffer);
1828 #[rustfmt::skip]
1829 let expected = Buffer::with_lines([
1830 "┌────────┐",
1831 "│ │",
1832 "└────────┘",
1833 ]);
1834 assert_eq!(buffer, expected);
1835
1836 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1837 Block::new()
1838 .borders(Borders::TOP | Borders::LEFT)
1839 .merge_borders(strategy)
1840 .render(buffer.area, &mut buffer);
1841 #[rustfmt::skip]
1842 let expected = Buffer::with_lines([
1843 "┌─────────",
1844 "│ ",
1845 "│ ",
1846 ]);
1847 assert_eq!(buffer, expected);
1848
1849 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1850 Block::new()
1851 .borders(Borders::TOP | Borders::RIGHT)
1852 .merge_borders(strategy)
1853 .render(buffer.area, &mut buffer);
1854 #[rustfmt::skip]
1855 let expected = Buffer::with_lines([
1856 "─────────┐",
1857 " │",
1858 " │",
1859 ]);
1860 assert_eq!(buffer, expected);
1861
1862 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1863 Block::new()
1864 .borders(Borders::BOTTOM | Borders::LEFT)
1865 .merge_borders(strategy)
1866 .render(buffer.area, &mut buffer);
1867 #[rustfmt::skip]
1868 let expected = Buffer::with_lines([
1869 "│ ",
1870 "│ ",
1871 "└─────────",
1872 ]);
1873 assert_eq!(buffer, expected);
1874
1875 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1876 Block::new()
1877 .borders(Borders::BOTTOM | Borders::RIGHT)
1878 .merge_borders(strategy)
1879 .render(buffer.area, &mut buffer);
1880 #[rustfmt::skip]
1881 let expected = Buffer::with_lines([
1882 " │",
1883 " │",
1884 "─────────┘",
1885 ]);
1886 assert_eq!(buffer, expected);
1887
1888 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1889 Block::new()
1890 .borders(Borders::TOP | Borders::BOTTOM)
1891 .merge_borders(strategy)
1892 .render(buffer.area, &mut buffer);
1893 #[rustfmt::skip]
1894 let expected = Buffer::with_lines([
1895 "──────────",
1896 " ",
1897 "──────────",
1898 ]);
1899 assert_eq!(buffer, expected);
1900
1901 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1902 Block::new()
1903 .borders(Borders::LEFT | Borders::RIGHT)
1904 .merge_borders(strategy)
1905 .render(buffer.area, &mut buffer);
1906 #[rustfmt::skip]
1907 let expected = Buffer::with_lines([
1908 "│ │",
1909 "│ │",
1910 "│ │",
1911 ]);
1912 assert_eq!(buffer, expected);
1913 }
1914
1915 #[rstest]
1922 #[case::replace(MergeStrategy::Replace, include_str!("../tests/block/merge_replace.txt"))]
1923 #[case::exact(MergeStrategy::Exact, include_str!("../tests/block/merge_exact.txt"))]
1924 #[case::fuzzy(MergeStrategy::Fuzzy, include_str!("../tests/block/merge_fuzzy.txt"))]
1925 fn render_merged_borders(#[case] strategy: MergeStrategy, #[case] expected: &'static str) {
1926 let border_types = [
1927 BorderType::Plain,
1928 BorderType::Rounded,
1929 BorderType::Thick,
1930 BorderType::Double,
1931 BorderType::LightDoubleDashed,
1932 BorderType::HeavyDoubleDashed,
1933 BorderType::LightTripleDashed,
1934 BorderType::HeavyTripleDashed,
1935 BorderType::LightQuadrupleDashed,
1936 BorderType::HeavyQuadrupleDashed,
1937 ];
1938 let rects = [
1939 (Rect::new(0, 0, 5, 5), Rect::new(4, 4, 5, 5)),
1941 (Rect::new(10, 0, 5, 5), Rect::new(12, 2, 5, 5)),
1943 (Rect::new(18, 0, 5, 5), Rect::new(22, 0, 5, 5)),
1945 (Rect::new(28, 0, 5, 5), Rect::new(28, 4, 5, 5)),
1947 ];
1948
1949 let mut buffer = Buffer::empty(Rect::new(0, 0, 43, 1000));
1950
1951 let mut offset = Offset::ZERO;
1952 for (border_type_1, border_type_2) in iproduct!(border_types, border_types) {
1953 let title = format!("{border_type_1} + {border_type_2}");
1954 let title_area = Rect::new(0, 0, 43, 1) + offset;
1955 title.render(title_area, &mut buffer);
1956 offset.y += 1;
1957 for (rect_1, rect_2) in rects {
1958 Block::bordered()
1959 .border_type(border_type_1)
1960 .merge_borders(strategy)
1961 .render(rect_1 + offset, &mut buffer);
1962 Block::bordered()
1963 .border_type(border_type_2)
1964 .merge_borders(strategy)
1965 .render(rect_2 + offset, &mut buffer);
1966 }
1967 offset.y += 9;
1968 }
1969 pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
1970 }
1971
1972 #[rstest]
1973 #[case::replace(MergeStrategy::Replace, Buffer::with_lines([
1974 "┏block top━━┓",
1975 "┃ ┃",
1976 "┗━━━━━━━━━━━┛",
1977 "│ │",
1978 "└───────────┘",
1979 ])
1980 )]
1981 #[case::replace(MergeStrategy::Exact, Buffer::with_lines([
1982 "┏block top━━┓",
1983 "┃ ┃",
1984 "┡block btm━━┩",
1985 "│ │",
1986 "└───────────┘",
1987 ])
1988 )]
1989 #[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
1990 "┏block top━━┓",
1991 "┃ ┃",
1992 "┡block btm━━┩",
1993 "│ │",
1994 "└───────────┘",
1995 ])
1996 )]
1997 fn merged_titles_bottom_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
1998 let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
1999 Block::bordered()
2000 .title("block btm")
2001 .render(Rect::new(0, 2, 13, 3), &mut buffer);
2002 Block::bordered()
2003 .title("block top")
2004 .border_type(BorderType::Thick)
2005 .merge_borders(strategy)
2006 .render(Rect::new(0, 0, 13, 3), &mut buffer);
2007 assert_eq!(buffer, expected);
2008 }
2009
2010 #[rstest]
2011 #[case::replace(MergeStrategy::Replace, Buffer::with_lines([
2012 "┏block top━━┓",
2013 "┃ ┃",
2014 "┌block btm──┐",
2015 "│ │",
2016 "└───────────┘",
2017 ])
2018 )]
2019 #[case::replace(MergeStrategy::Exact, Buffer::with_lines([
2020 "┏block top━━┓",
2021 "┃ ┃",
2022 "┞block btm──┦",
2023 "│ │",
2024 "└───────────┘",
2025 ])
2026 )]
2027 #[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
2028 "┏block top━━┓",
2029 "┃ ┃",
2030 "┞block btm──┦",
2031 "│ │",
2032 "└───────────┘",
2033 ])
2034 )]
2035 fn merged_titles_top_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
2036 let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
2037 Block::bordered()
2038 .title("block top")
2039 .border_type(BorderType::Thick)
2040 .render(Rect::new(0, 0, 13, 3), &mut buffer);
2041 Block::bordered()
2042 .title("block btm")
2043 .merge_borders(strategy)
2044 .render(Rect::new(0, 2, 13, 3), &mut buffer);
2045 assert_eq!(buffer, expected);
2046 }
2047
2048 #[test]
2049 fn left_titles() {
2050 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2051 Block::new()
2052 .title("L12")
2053 .title("L34")
2054 .render(buffer.area, &mut buffer);
2055 assert_eq!(buffer, Buffer::with_lines(["L12 L34 "]));
2056 }
2057
2058 #[test]
2059 fn left_titles_truncated() {
2060 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2061 Block::new()
2062 .title("L12345")
2063 .title("L67890")
2064 .render(buffer.area, &mut buffer);
2065 assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
2066 }
2067
2068 #[test]
2069 fn center_titles() {
2070 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2071 Block::new()
2072 .title(Line::from("C12").centered())
2073 .title(Line::from("C34").centered())
2074 .render(buffer.area, &mut buffer);
2075 assert_eq!(buffer, Buffer::with_lines([" C12 C34 "]));
2076 }
2077
2078 #[test]
2079 fn center_titles_truncated() {
2080 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2081 Block::new()
2082 .title(Line::from("C12345").centered())
2083 .title(Line::from("C67890").centered())
2084 .render(buffer.area, &mut buffer);
2085 assert_eq!(buffer, Buffer::with_lines(["12345 C678"]));
2086 }
2087
2088 #[test]
2089 fn right_titles() {
2090 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2091 Block::new()
2092 .title(Line::from("R12").right_aligned())
2093 .title(Line::from("R34").right_aligned())
2094 .render(buffer.area, &mut buffer);
2095 assert_eq!(buffer, Buffer::with_lines([" R12 R34"]));
2096 }
2097
2098 #[test]
2099 fn right_titles_truncated() {
2100 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2101 Block::new()
2102 .title(Line::from("R12345").right_aligned())
2103 .title(Line::from("R67890").right_aligned())
2104 .render(buffer.area, &mut buffer);
2105 assert_eq!(buffer, Buffer::with_lines(["345 R67890"]));
2106 }
2107
2108 #[test]
2109 fn center_title_truncates_left_title() {
2110 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2111 Block::new()
2112 .title("L1234")
2113 .title(Line::from("C5678").centered())
2114 .render(buffer.area, &mut buffer);
2115 assert_eq!(buffer, Buffer::with_lines(["L1C5678 "]));
2116 }
2117
2118 #[test]
2119 fn right_title_truncates_left_title() {
2120 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2121 Block::new()
2122 .title("L12345")
2123 .title(Line::from("R67890").right_aligned())
2124 .render(buffer.area, &mut buffer);
2125 assert_eq!(buffer, Buffer::with_lines(["L123R67890"]));
2126 }
2127
2128 #[test]
2129 fn right_title_truncates_center_title() {
2130 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2131 Block::new()
2132 .title(Line::from("C12345").centered())
2133 .title(Line::from("R67890").right_aligned())
2134 .render(buffer.area, &mut buffer);
2135 assert_eq!(buffer, Buffer::with_lines([" C1R67890"]));
2136 }
2137
2138 #[test]
2139 fn render_in_minimal_buffer() {
2140 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2141 Block::bordered()
2143 .title("I'm too big for this buffer")
2144 .padding(Padding::uniform(10))
2145 .render(buffer.area, &mut buffer);
2146 assert_eq!(buffer, Buffer::with_lines(["┌"]));
2147 }
2148
2149 #[test]
2150 fn render_in_zero_size_buffer() {
2151 let mut buffer = Buffer::empty(Rect::ZERO);
2152 Block::bordered()
2154 .title("I'm too big for this buffer")
2155 .padding(Padding::uniform(10))
2156 .render(buffer.area, &mut buffer);
2157 }
2158}