use crate::box_chars::BoxChars;
use crate::errors::Result;
use crate::measure::{Measurable, MeasureOptions, Measurement, cell_len};
use crate::segment::{Segment, Segments};
use crate::style::Style;
use crate::text::{Justify, Text};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub enum VerticalAlign {
#[default]
Top,
Middle,
Bottom,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Column {
pub header: Option<Text>,
pub footer: Option<Text>,
pub header_style: Option<Style>,
pub footer_style: Option<Style>,
pub style: Option<Style>,
pub justify: Justify,
pub vertical: VerticalAlign,
pub width: Option<usize>,
pub min_width: Option<usize>,
pub max_width: Option<usize>,
pub ratio: Option<f32>,
pub no_wrap: bool,
pub highlight: bool,
}
impl Column {
#[inline]
#[must_use]
pub fn new(header: impl Into<Text>) -> Self {
Self {
header: Some(header.into()),
..Self::default()
}
}
#[inline]
#[must_use]
pub const fn empty() -> Self {
Self {
header: None,
footer: None,
header_style: None,
footer_style: None,
style: None,
justify: Justify::Left,
vertical: VerticalAlign::Top,
width: None,
min_width: None,
max_width: None,
ratio: None,
no_wrap: false,
highlight: false,
}
}
#[inline]
#[must_use]
pub fn header_style(mut self, style: Style) -> Self {
self.header_style = Some(style);
self
}
#[inline]
#[must_use]
pub fn footer(mut self, footer: impl Into<Text>) -> Self {
self.footer = Some(footer.into());
self
}
#[inline]
#[must_use]
pub fn footer_style(mut self, style: Style) -> Self {
self.footer_style = Some(style);
self
}
#[inline]
#[must_use]
pub fn style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
#[inline]
#[must_use]
pub const fn justify(mut self, justify: Justify) -> Self {
self.justify = justify;
self
}
#[inline]
#[must_use]
pub const fn vertical(mut self, vertical: VerticalAlign) -> Self {
self.vertical = vertical;
self
}
#[inline]
#[must_use]
pub const fn width(mut self, width: usize) -> Self {
self.width = Some(width);
self
}
#[inline]
#[must_use]
pub const fn min_width(mut self, min_width: usize) -> Self {
self.min_width = Some(min_width);
self
}
#[inline]
#[must_use]
pub const fn max_width(mut self, max_width: usize) -> Self {
self.max_width = Some(max_width);
self
}
#[inline]
#[must_use]
pub const fn ratio(mut self, ratio: f32) -> Self {
self.ratio = Some(ratio);
self
}
#[inline]
#[must_use]
pub const fn no_wrap(mut self, no_wrap: bool) -> Self {
self.no_wrap = no_wrap;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct Row {
pub cells: Vec<Text>,
pub style: Option<Style>,
pub end_section: bool,
}
impl Row {
#[must_use]
pub fn new<I, T>(cells: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<Text>,
{
Self {
cells: cells.into_iter().map(Into::into).collect(),
style: None,
end_section: false,
}
}
#[inline]
#[must_use]
pub fn style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
#[allow(dead_code)]
pub struct Table {
columns: Vec<Column>,
rows: Vec<Row>,
box_chars: Option<BoxChars>,
title: Option<Text>,
caption: Option<Text>,
title_style: Option<Style>,
caption_style: Option<Style>,
border_style: Option<Style>,
header_style: Option<Style>,
footer_style: Option<Style>,
row_styles: Vec<Style>,
style: Option<Style>,
title_justify: Justify,
caption_justify: Justify,
show_header: bool,
show_footer: bool,
show_edge: bool,
show_lines: bool,
expand: bool,
padding: (usize, usize),
collapse_padding: bool,
pad_edge: bool,
leading: usize,
min_width: Option<usize>,
width: Option<usize>,
safe_box: bool,
highlight: bool,
}
impl Table {
#[must_use]
pub fn new() -> Self {
Self {
columns: Vec::new(),
rows: Vec::new(),
box_chars: Some(BoxChars::SQUARE),
title: None,
caption: None,
title_style: None,
caption_style: None,
border_style: None,
header_style: None,
footer_style: None,
row_styles: Vec::new(),
style: None,
title_justify: Justify::Center,
caption_justify: Justify::Center,
show_header: true,
show_footer: true,
show_edge: true,
show_lines: false,
expand: false,
padding: (1, 1),
collapse_padding: false,
pad_edge: true,
leading: 0,
min_width: None,
width: None,
safe_box: false,
highlight: false,
}
}
#[must_use]
pub fn grid() -> Self {
Self {
box_chars: None,
show_header: false,
show_edge: false,
..Self::new()
}
}
#[inline]
pub fn add_column(&mut self, column: Column) {
self.columns.push(column);
}
#[inline]
pub fn add_column_str(&mut self, header: &str) {
self.columns.push(Column::new(header));
}
#[inline]
pub fn add_row(&mut self, row: Row) {
self.rows.push(row);
}
pub fn add_row_cells<I, T>(&mut self, cells: I)
where
I: IntoIterator<Item = T>,
T: Into<Text>,
{
self.rows.push(Row::new(cells));
}
pub fn add_section(&mut self) {
if let Some(last) = self.rows.last_mut() {
last.end_section = true;
}
}
#[inline]
#[must_use]
pub fn box_chars(mut self, box_chars: Option<BoxChars>) -> Self {
self.box_chars = box_chars;
self
}
#[inline]
#[must_use]
pub fn title(mut self, title: impl Into<Text>) -> Self {
self.title = Some(title.into());
self
}
#[inline]
#[must_use]
pub fn caption(mut self, caption: impl Into<Text>) -> Self {
self.caption = Some(caption.into());
self
}
#[inline]
#[must_use]
pub fn border_style(mut self, style: Style) -> Self {
self.border_style = Some(style);
self
}
#[inline]
#[must_use]
pub fn header_style(mut self, style: Style) -> Self {
self.header_style = Some(style);
self
}
#[inline]
#[must_use]
pub const fn show_header(mut self, show: bool) -> Self {
self.show_header = show;
self
}
#[inline]
#[must_use]
pub const fn show_footer(mut self, show: bool) -> Self {
self.show_footer = show;
self
}
#[inline]
#[must_use]
pub const fn show_edge(mut self, show: bool) -> Self {
self.show_edge = show;
self
}
#[inline]
#[must_use]
pub const fn show_lines(mut self, show: bool) -> Self {
self.show_lines = show;
self
}
#[inline]
#[must_use]
pub const fn expand(mut self, expand: bool) -> Self {
self.expand = expand;
self
}
#[inline]
#[must_use]
pub const fn padding(mut self, horizontal: usize, vertical: usize) -> Self {
self.padding = (horizontal, vertical);
self
}
#[inline]
#[must_use]
pub fn safe_box(mut self, safe: bool) -> Self {
self.safe_box = safe;
if safe {
self.box_chars = Some(BoxChars::ASCII);
}
self
}
#[inline]
#[must_use]
pub const fn width(mut self, width: usize) -> Self {
self.width = Some(width);
self
}
#[inline]
#[must_use]
pub const fn min_width(mut self, min_width: usize) -> Self {
self.min_width = Some(min_width);
self
}
#[inline]
#[must_use]
pub fn column_count(&self) -> usize {
self.columns.len()
}
#[inline]
#[must_use]
pub fn row_count(&self) -> usize {
self.rows.len()
}
fn calculate_column_widths(&self, max_width: usize) -> Vec<usize> {
if self.columns.is_empty() {
return Vec::new();
}
let col_count = self.columns.len();
let mut widths = vec![0usize; col_count];
for (idx, col) in self.columns.iter().enumerate() {
if let Some(ref header) = col.header {
widths[idx] = widths
.get(idx)
.copied()
.unwrap_or(0)
.max(cell_len(header.plain()));
}
if let Some(w) = col.width {
widths[idx] = w;
} else if let Some(min_w) = col.min_width {
widths[idx] = widths.get(idx).copied().unwrap_or(0).max(min_w);
}
}
for row in &self.rows {
for (idx, cell) in row.cells.iter().enumerate() {
if idx < col_count {
let cell_width = cell_len(cell.plain());
widths[idx] = widths.get(idx).copied().unwrap_or(0).max(cell_width);
}
}
}
for (idx, col) in self.columns.iter().enumerate() {
if let Some(max_w) = col.max_width {
if let Some(w) = widths.get_mut(idx) {
*w = (*w).min(max_w);
}
}
}
let border_overhead = if self.box_chars.is_some() {
col_count.saturating_add(1)
} else {
0
};
let padding_overhead = col_count.saturating_mul(self.padding.0.saturating_mul(2));
let available = max_width
.saturating_sub(border_overhead)
.saturating_sub(padding_overhead);
let total: usize = widths.iter().sum();
if total > available && !widths.is_empty() {
for w in &mut widths {
*w = (*w)
.saturating_mul(available)
.checked_div(total)
.unwrap_or(*w)
.max(1);
}
}
widths
}
#[must_use]
pub fn render(&self, max_width: usize) -> Segments {
let mut segments = Segments::new();
if self.columns.is_empty() {
return segments;
}
let widths = self.calculate_column_widths(max_width);
let box_chars = self.box_chars.as_ref();
if let Some(ref title) = self.title {
self.render_title(&mut segments, title, &widths);
}
if let Some(bc) = box_chars {
if self.show_edge {
self.render_top_border(&mut segments, bc, &widths);
}
}
if self.show_header {
self.render_header(&mut segments, &widths, box_chars);
}
for (row_idx, row) in self.rows.iter().enumerate() {
self.render_row(&mut segments, row, row_idx, &widths, box_chars);
if row.end_section && row_idx < self.rows.len().saturating_sub(1) {
if let Some(bc) = box_chars {
self.render_row_separator(&mut segments, bc, &widths);
}
}
}
if let Some(bc) = box_chars {
if self.show_edge {
self.render_bottom_border(&mut segments, bc, &widths);
}
}
if let Some(ref caption) = self.caption {
self.render_caption(&mut segments, caption, &widths);
}
segments
}
fn render_title(&self, segments: &mut Segments, title: &Text, widths: &[usize]) {
let total_width = self.calculate_total_width(widths);
let title_text = title.plain();
let title_width = cell_len(title_text);
let padding = total_width.saturating_sub(title_width);
let left_pad = padding.checked_div(2).unwrap_or(0);
let right_pad = padding.saturating_sub(left_pad);
segments.push(Segment::new(" ".repeat(left_pad)));
segments.extend(title.to_segments());
segments.push(Segment::new(" ".repeat(right_pad)));
segments.push(Segment::newline());
}
fn render_caption(&self, segments: &mut Segments, caption: &Text, widths: &[usize]) {
let total_width = self.calculate_total_width(widths);
let caption_text = caption.plain();
let caption_width = cell_len(caption_text);
let padding = total_width.saturating_sub(caption_width);
let left_pad = padding.checked_div(2).unwrap_or(0);
let right_pad = padding.saturating_sub(left_pad);
segments.push(Segment::new(" ".repeat(left_pad)));
segments.extend(caption.to_segments());
segments.push(Segment::new(" ".repeat(right_pad)));
segments.push(Segment::newline());
}
fn calculate_total_width(&self, widths: &[usize]) -> usize {
let content: usize = widths.iter().sum();
let padding = widths
.len()
.saturating_mul(self.padding.0.saturating_mul(2));
let borders = if self.box_chars.is_some() {
widths.len().saturating_add(1)
} else {
0
};
content.saturating_add(padding).saturating_add(borders)
}
fn render_top_border(&self, segments: &mut Segments, bc: &BoxChars, widths: &[usize]) {
let style = self.border_style.clone();
let corner = bc.top_left.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(corner, s.clone()));
} else {
segments.push(Segment::new(corner));
}
for (idx, width) in widths.iter().enumerate() {
let line_width = width.saturating_add(self.padding.0.saturating_mul(2));
let line = bc.horizontal.to_string().repeat(line_width);
if let Some(ref s) = style {
segments.push(Segment::styled(line, s.clone()));
} else {
segments.push(Segment::new(line));
}
if idx < widths.len().saturating_sub(1) {
let junction = bc.horizontal_down.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(junction, s.clone()));
} else {
segments.push(Segment::new(junction));
}
}
}
let corner = bc.top_right.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(corner, s.clone()));
} else {
segments.push(Segment::new(corner));
}
segments.push(Segment::newline());
}
fn render_bottom_border(&self, segments: &mut Segments, bc: &BoxChars, widths: &[usize]) {
let style = self.border_style.clone();
let corner = bc.bottom_left.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(corner, s.clone()));
} else {
segments.push(Segment::new(corner));
}
for (idx, width) in widths.iter().enumerate() {
let line_width = width.saturating_add(self.padding.0.saturating_mul(2));
let line = bc.horizontal.to_string().repeat(line_width);
if let Some(ref s) = style {
segments.push(Segment::styled(line, s.clone()));
} else {
segments.push(Segment::new(line));
}
if idx < widths.len().saturating_sub(1) {
let junction = bc.horizontal_up.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(junction, s.clone()));
} else {
segments.push(Segment::new(junction));
}
}
}
let corner = bc.bottom_right.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(corner, s.clone()));
} else {
segments.push(Segment::new(corner));
}
segments.push(Segment::newline());
}
fn render_row_separator(&self, segments: &mut Segments, bc: &BoxChars, widths: &[usize]) {
let style = self.border_style.clone();
let junction = bc.vertical_right.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(junction, s.clone()));
} else {
segments.push(Segment::new(junction));
}
for (idx, width) in widths.iter().enumerate() {
let line_width = width.saturating_add(self.padding.0.saturating_mul(2));
let line = bc.horizontal.to_string().repeat(line_width);
if let Some(ref s) = style {
segments.push(Segment::styled(line, s.clone()));
} else {
segments.push(Segment::new(line));
}
if idx < widths.len().saturating_sub(1) {
let junction = bc.cross.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(junction, s.clone()));
} else {
segments.push(Segment::new(junction));
}
}
}
let junction = bc.vertical_left.to_string();
if let Some(ref s) = style {
segments.push(Segment::styled(junction, s.clone()));
} else {
segments.push(Segment::new(junction));
}
segments.push(Segment::newline());
}
fn render_header(
&self,
segments: &mut Segments,
widths: &[usize],
box_chars: Option<&BoxChars>,
) {
let border_style = self.border_style.clone();
let header_style = self.header_style.clone();
if let Some(bc) = box_chars {
let vertical = bc.vertical.to_string();
if let Some(ref s) = border_style {
segments.push(Segment::styled(vertical, s.clone()));
} else {
segments.push(Segment::new(vertical));
}
}
for (idx, col) in self.columns.iter().enumerate() {
let width = widths.get(idx).copied().unwrap_or(0);
segments.push(Segment::new(" ".repeat(self.padding.0)));
if let Some(ref header) = col.header {
let header_text = header.plain();
let header_len = cell_len(header_text);
let remaining = width.saturating_sub(header_len);
let header_segs = header.to_segments();
let style = col.header_style.clone().or_else(|| header_style.clone());
if let Some(s) = style {
for seg in header_segs.iter() {
let combined = seg
.style
.clone()
.map(|ss| ss.combine(&s))
.unwrap_or_else(|| s.clone());
segments.push(Segment::styled(seg.text.clone(), combined));
}
} else {
segments.extend(header_segs);
}
segments.push(Segment::new(" ".repeat(remaining)));
} else {
segments.push(Segment::new(" ".repeat(width)));
}
segments.push(Segment::new(" ".repeat(self.padding.0)));
if let Some(bc) = box_chars {
if idx < widths.len().saturating_sub(1) {
let vertical = bc.vertical.to_string();
if let Some(ref s) = border_style {
segments.push(Segment::styled(vertical, s.clone()));
} else {
segments.push(Segment::new(vertical));
}
}
}
}
if let Some(bc) = box_chars {
let vertical = bc.vertical.to_string();
if let Some(ref s) = border_style {
segments.push(Segment::styled(vertical, s.clone()));
} else {
segments.push(Segment::new(vertical));
}
}
segments.push(Segment::newline());
if let Some(bc) = box_chars {
self.render_row_separator(segments, bc, widths);
}
}
fn render_row(
&self,
segments: &mut Segments,
row: &Row,
_row_idx: usize,
widths: &[usize],
box_chars: Option<&BoxChars>,
) {
let border_style = self.border_style.clone();
if let Some(bc) = box_chars {
let vertical = bc.vertical.to_string();
if let Some(ref s) = border_style {
segments.push(Segment::styled(vertical, s.clone()));
} else {
segments.push(Segment::new(vertical));
}
}
for (idx, width) in widths.iter().enumerate() {
segments.push(Segment::new(" ".repeat(self.padding.0)));
if let Some(cell) = row.cells.get(idx) {
let cell_text = cell.plain();
let cell_len = cell_len(cell_text);
let remaining = width.saturating_sub(cell_len);
let cell_segs = cell.to_segments();
let style = row
.style
.clone()
.or_else(|| self.columns.get(idx).and_then(|c| c.style.clone()));
if let Some(s) = style {
for seg in cell_segs.iter() {
let combined = seg
.style
.clone()
.map(|ss| ss.combine(&s))
.unwrap_or_else(|| s.clone());
segments.push(Segment::styled(seg.text.clone(), combined));
}
} else {
segments.extend(cell_segs);
}
segments.push(Segment::new(" ".repeat(remaining)));
} else {
segments.push(Segment::new(" ".repeat(*width)));
}
segments.push(Segment::new(" ".repeat(self.padding.0)));
if let Some(bc) = box_chars {
if idx < widths.len().saturating_sub(1) {
let vertical = bc.vertical.to_string();
if let Some(ref s) = border_style {
segments.push(Segment::styled(vertical, s.clone()));
} else {
segments.push(Segment::new(vertical));
}
}
}
}
if let Some(bc) = box_chars {
let vertical = bc.vertical.to_string();
if let Some(ref s) = border_style {
segments.push(Segment::styled(vertical, s.clone()));
} else {
segments.push(Segment::new(vertical));
}
}
segments.push(Segment::newline());
}
}
impl Measurable for Table {
fn measure(&self, options: &MeasureOptions) -> Result<Measurement> {
let widths = self.calculate_column_widths(options.max_width);
let total = self.calculate_total_width(&widths);
Ok(Measurement::fixed(total).clamp_max(options.max_width))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::{Color, StandardColor};
#[test]
fn test_table_new() {
let table = Table::new();
assert_eq!(table.column_count(), 0);
assert_eq!(table.row_count(), 0);
assert!(table.show_header);
assert!(table.show_edge);
assert!(table.box_chars.is_some());
}
#[test]
fn test_table_add_column() {
let mut table = Table::new();
table.add_column(Column::new("Name"));
table.add_column(Column::new("Value"));
assert_eq!(table.column_count(), 2);
}
#[test]
fn test_table_add_row() {
let mut table = Table::new();
table.add_column(Column::new("Name"));
table.add_column(Column::new("Value"));
table.add_row_cells(["Alice", "100"]);
table.add_row_cells(["Bob", "200"]);
assert_eq!(table.row_count(), 2);
}
#[test]
fn test_table_add_row_object() {
let mut table = Table::new();
table.add_column(Column::new("A"));
table.add_column(Column::new("B"));
let row = Row::new(["x", "y"]);
table.add_row(row);
assert_eq!(table.row_count(), 1);
}
#[test]
fn test_table_render() {
let mut table = Table::new();
table.add_column(Column::new("Name"));
table.add_column(Column::new("Value"));
table.add_row_cells(["Alice", "100"]);
let segments = table.render(80);
let text = segments.plain_text();
assert!(text.contains("Name"));
assert!(text.contains("Value"));
assert!(text.contains("Alice"));
assert!(text.contains("100"));
}
#[test]
fn test_table_render_empty() {
let table = Table::new();
let segments = table.render(80);
assert!(segments.is_empty());
}
#[test]
fn test_table_grid() {
let table = Table::grid();
assert!(!table.show_header);
assert!(!table.show_edge);
assert!(table.box_chars.is_none());
}
#[test]
fn test_column_builder() {
let col = Column::new("Test")
.justify(Justify::Right)
.width(20)
.no_wrap(true);
assert!(col.header.is_some());
assert_eq!(col.justify, Justify::Right);
assert_eq!(col.width, Some(20));
assert!(col.no_wrap);
}
#[test]
fn test_column_style() {
let style = Style::new().bold();
let col = Column::new("Test").style(style);
assert!(col.style.is_some());
}
#[test]
fn test_column_header_style() {
let style = Style::new().italic();
let col = Column::new("Test").header_style(style);
assert!(col.header_style.is_some());
}
#[test]
fn test_column_min_max_width() {
let col = Column::new("Test").min_width(5).max_width(50);
assert_eq!(col.min_width, Some(5));
assert_eq!(col.max_width, Some(50));
}
#[test]
fn test_column_ratio() {
let col = Column::new("Test").ratio(2.0);
assert_eq!(col.ratio, Some(2.0));
}
#[test]
fn test_row_builder() {
let style = Style::new().bold();
let row = Row::new(["a", "b", "c"]).style(style.clone());
assert_eq!(row.cells.len(), 3);
assert!(row.style.is_some());
}
#[test]
fn test_row_end_section_field() {
let mut row = Row::new(["a"]);
row.end_section = true;
assert!(row.end_section);
}
#[test]
fn test_table_safe_box() {
let table = Table::new().safe_box(true);
assert!(table.safe_box);
assert_eq!(table.box_chars, Some(BoxChars::ASCII));
}
#[test]
fn test_table_safe_box_false() {
let table = Table::new().safe_box(false);
assert!(!table.safe_box);
}
#[test]
fn test_table_show_header() {
let table = Table::new().show_header(false);
assert!(!table.show_header);
}
#[test]
fn test_table_show_edge() {
let table = Table::new().show_edge(false);
assert!(!table.show_edge);
}
#[test]
fn test_table_header_style() {
let style = Style::new().bold();
let table = Table::new().header_style(style);
assert!(table.header_style.is_some());
}
#[test]
fn test_table_border_style() {
let style = Style::new().with_color(Color::Standard(StandardColor::Blue));
let table = Table::new().border_style(style);
assert!(table.border_style.is_some());
}
#[test]
fn test_table_title() {
let table = Table::new().title("My Table");
assert!(table.title.is_some());
}
#[test]
fn test_table_caption() {
let table = Table::new().caption("Table caption");
assert!(table.caption.is_some());
}
#[test]
fn test_table_expand() {
let table = Table::new().expand(true);
assert!(table.expand);
}
#[test]
fn test_table_padding() {
let table = Table::new().padding(2, 1);
assert_eq!(table.padding, (2, 1));
}
#[test]
fn test_table_box_chars() {
let table = Table::new().box_chars(Some(BoxChars::ASCII));
assert_eq!(table.box_chars, Some(BoxChars::ASCII));
}
#[test]
fn test_table_render_with_title() {
let mut table = Table::new().title("Title");
table.add_column(Column::new("A"));
table.add_row_cells(["x"]);
let segments = table.render(40);
let text = segments.plain_text();
assert!(text.contains("Title"));
}
#[test]
fn test_table_render_with_caption() {
let mut table = Table::new().caption("Caption");
table.add_column(Column::new("A"));
table.add_row_cells(["x"]);
let segments = table.render(40);
let text = segments.plain_text();
assert!(text.contains("Caption"));
}
#[test]
fn test_table_render_no_edge() {
let mut table = Table::new().show_edge(false);
table.add_column(Column::new("A"));
table.add_row_cells(["x"]);
let segments = table.render(40);
assert!(!segments.is_empty());
}
#[test]
fn test_table_render_no_header() {
let mut table = Table::new().show_header(false);
table.add_column(Column::new("Header"));
table.add_row_cells(["data"]);
let segments = table.render(40);
let text = segments.plain_text();
assert!(text.contains("data"));
}
#[test]
fn test_table_render_with_border_style() {
let style = Style::new().with_color(Color::Standard(StandardColor::Red));
let mut table = Table::new().border_style(style);
table.add_column(Column::new("A"));
table.add_row_cells(["x"]);
let segments = table.render(40);
assert!(!segments.is_empty());
}
#[test]
fn test_table_render_multiple_rows() {
let mut table = Table::new();
table.add_column(Column::new("Name"));
table.add_column(Column::new("Value"));
table.add_row_cells(["A", "1"]);
table.add_row_cells(["B", "2"]);
table.add_row_cells(["C", "3"]);
let segments = table.render(40);
let text = segments.plain_text();
assert!(text.contains("A"));
assert!(text.contains("B"));
assert!(text.contains("C"));
}
#[test]
fn test_table_render_with_row_style() {
let mut table = Table::new();
table.add_column(Column::new("A"));
let styled_row = Row::new(["styled"]).style(Style::new().bold());
table.add_row(styled_row);
let segments = table.render(40);
let text = segments.plain_text();
assert!(text.contains("styled"));
}
#[test]
fn test_table_render_with_column_style() {
let mut table = Table::new();
let col = Column::new("Styled").style(Style::new().italic());
table.add_column(col);
table.add_row_cells(["data"]);
let segments = table.render(40);
assert!(!segments.is_empty());
}
#[test]
fn test_table_render_grid() {
let mut table = Table::grid();
table.add_column(Column::new("A"));
table.add_column(Column::new("B"));
table.add_row_cells(["x", "y"]);
let segments = table.render(40);
let text = segments.plain_text();
assert!(text.contains("x"));
assert!(text.contains("y"));
}
#[test]
fn test_table_render_with_section() {
let mut table = Table::new();
table.add_column(Column::new("A"));
let mut row = Row::new(["first"]);
row.end_section = true;
table.add_row(row);
table.add_row_cells(["second"]);
let segments = table.render(40);
assert!(!segments.is_empty());
}
#[test]
fn test_table_measure() {
let mut table = Table::new();
table.add_column(Column::new("Name"));
table.add_row_cells(["Test"]);
let options = MeasureOptions::new(80);
let measurement = table.measure(&options).ok().unwrap_or_default();
assert!(measurement.minimum > 0);
}
#[test]
fn test_table_render_narrow() {
let mut table = Table::new();
table.add_column(Column::new("A"));
table.add_row_cells(["x"]);
let segments = table.render(10);
assert!(!segments.is_empty());
}
#[test]
fn test_column_without_header() {
let col = Column::empty();
assert!(col.header.is_none());
}
}