use super::cell_style::CellStyle;
use super::error::TableError;
use super::header_builder::HeaderBuilder;
use crate::graphics::Color;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Column {
pub header: String,
pub width: f64,
pub default_style: Option<CellStyle>,
pub auto_resize: bool,
pub min_width: Option<f64>,
pub max_width: Option<f64>,
}
impl Column {
pub fn new<S: Into<String>>(header: S, width: f64) -> Self {
Self {
header: header.into(),
width,
default_style: None,
auto_resize: false,
min_width: None,
max_width: None,
}
}
pub fn with_style(mut self, style: CellStyle) -> Self {
self.default_style = Some(style);
self
}
pub fn auto_resize(mut self, min_width: Option<f64>, max_width: Option<f64>) -> Self {
self.auto_resize = true;
self.min_width = min_width;
self.max_width = max_width;
self
}
}
#[derive(Debug, Clone)]
pub struct CellData {
pub content: String,
pub style: Option<CellStyle>,
pub colspan: usize,
pub rowspan: usize,
}
impl CellData {
pub fn new<S: Into<String>>(content: S) -> Self {
Self {
content: content.into(),
style: None,
colspan: 1,
rowspan: 1,
}
}
pub fn with_style(mut self, style: CellStyle) -> Self {
self.style = Some(style);
self
}
pub fn colspan(mut self, span: usize) -> Self {
self.colspan = span.max(1);
self
}
pub fn rowspan(mut self, span: usize) -> Self {
self.rowspan = span.max(1);
self
}
}
#[derive(Debug, Clone)]
pub struct RowData {
pub cells: Vec<CellData>,
pub style: Option<CellStyle>,
pub min_height: Option<f64>,
}
impl RowData {
pub fn from_strings(content: Vec<&str>) -> Self {
let cells = content.into_iter().map(CellData::new).collect();
Self {
cells,
style: None,
min_height: None,
}
}
pub fn from_cells(cells: Vec<CellData>) -> Self {
Self {
cells,
style: None,
min_height: None,
}
}
pub fn with_style(mut self, style: CellStyle) -> Self {
self.style = Some(style);
self
}
pub fn min_height(mut self, height: f64) -> Self {
self.min_height = Some(height);
self
}
}
#[derive(Debug, Clone)]
pub struct AdvancedTable {
pub title: Option<String>,
pub x: f64,
pub y: f64,
pub columns: Vec<Column>,
pub rows: Vec<RowData>,
pub header: Option<HeaderBuilder>,
pub show_header: bool,
pub default_style: CellStyle,
pub header_style: CellStyle,
pub zebra_striping: Option<ZebraConfig>,
pub table_border: bool,
pub cell_spacing: f64,
pub total_width: Option<f64>,
pub repeat_headers: bool,
pub cell_styles: HashMap<(usize, usize), CellStyle>,
}
#[derive(Debug, Clone)]
pub struct ZebraConfig {
pub odd_color: Option<Color>,
pub even_color: Option<Color>,
pub start_with_odd: bool,
}
impl ZebraConfig {
pub fn new(odd_color: Option<Color>, even_color: Option<Color>) -> Self {
Self {
odd_color,
even_color,
start_with_odd: true,
}
}
pub fn simple(color: Color) -> Self {
Self::new(Some(color), None)
}
pub fn get_color_for_row(&self, row_index: usize) -> Option<Color> {
let is_odd = (row_index % 2) == (if self.start_with_odd { 1 } else { 0 });
if is_odd {
self.odd_color
} else {
self.even_color
}
}
}
pub struct AdvancedTableBuilder {
table: AdvancedTable,
}
impl AdvancedTableBuilder {
pub fn new() -> Self {
Self {
table: AdvancedTable {
title: None,
x: 0.0,
y: 0.0,
columns: Vec::new(),
rows: Vec::new(),
header: None,
show_header: true,
default_style: CellStyle::data(),
header_style: CellStyle::header(),
zebra_striping: None,
table_border: true,
cell_spacing: 0.0,
total_width: None,
repeat_headers: false,
cell_styles: HashMap::new(),
},
}
}
pub fn add_column<S: Into<String>>(mut self, header: S, width: f64) -> Self {
self.table.columns.push(Column::new(header, width));
self
}
pub fn add_styled_column<S: Into<String>>(
mut self,
header: S,
width: f64,
style: CellStyle,
) -> Self {
self.table
.columns
.push(Column::new(header, width).with_style(style));
self
}
pub fn columns_equal_width(mut self, headers: Vec<&str>, total_width: f64) -> Self {
let column_width = total_width / headers.len() as f64;
self.table.columns = headers
.into_iter()
.map(|header| Column::new(header, column_width))
.collect();
self.table.total_width = Some(total_width);
self
}
pub fn add_row(mut self, content: Vec<&str>) -> Self {
self.table.rows.push(RowData::from_strings(content));
self
}
pub fn add_row_with_min_height(mut self, content: Vec<&str>, min_height: f64) -> Self {
self.table
.rows
.push(RowData::from_strings(content).min_height(min_height));
self
}
pub fn add_row_cells(mut self, cells: Vec<CellData>) -> Self {
self.table.rows.push(RowData::from_cells(cells));
self
}
pub fn add_styled_row(mut self, content: Vec<&str>, style: CellStyle) -> Self {
self.table
.rows
.push(RowData::from_strings(content).with_style(style));
self
}
pub fn default_style(mut self, style: CellStyle) -> Self {
self.table.default_style = style;
self
}
pub fn data_style(mut self, style: CellStyle) -> Self {
self.table.default_style = style;
self
}
pub fn header_style(mut self, style: CellStyle) -> Self {
self.table.header_style = style;
self
}
pub fn show_header(mut self, show: bool) -> Self {
self.table.show_header = show;
self
}
pub fn title<S: Into<String>>(mut self, title: S) -> Self {
self.table.title = Some(title.into());
self
}
pub fn columns(mut self, column_specs: Vec<(&str, f64)>) -> Self {
self.table.columns = column_specs
.into_iter()
.map(|(header, width)| Column::new(header, width))
.collect();
self
}
pub fn position(mut self, x: f64, y: f64) -> Self {
self.table.x = x;
self.table.y = y;
self
}
pub fn complex_header(mut self, header: HeaderBuilder) -> Self {
if self.table.columns.is_empty() {
let column_count = header.total_columns;
for i in 0..column_count {
self.table.columns.push(Column::new(
format!("Column {}", i + 1),
100.0, ));
}
}
self.table.header = Some(header);
self
}
pub fn zebra_stripes(mut self, enabled: bool, color: Color) -> Self {
if enabled {
self.table.zebra_striping = Some(ZebraConfig::simple(color));
} else {
self.table.zebra_striping = None;
}
self
}
pub fn add_row_with_style(mut self, content: Vec<&str>, style: CellStyle) -> Self {
let mut row = RowData::from_strings(content);
row = row.with_style(style);
self.table.rows.push(row);
self
}
pub fn add_row_with_mixed_styles(mut self, cells: Vec<(CellStyle, &str)>) -> Self {
let cell_data: Vec<CellData> = cells
.into_iter()
.map(|(style, content)| CellData::new(content.to_string()).with_style(style))
.collect();
self.table.rows.push(RowData::from_cells(cell_data));
self
}
pub fn build(self) -> Result<AdvancedTable, TableError> {
if self.table.columns.is_empty() {
return Err(TableError::NoColumns);
}
Ok(self.table)
}
pub fn zebra_striping(mut self, color: Color) -> Self {
self.table.zebra_striping = Some(ZebraConfig::simple(color));
self
}
pub fn zebra_striping_custom(mut self, config: ZebraConfig) -> Self {
self.table.zebra_striping = Some(config);
self
}
pub fn table_border(mut self, enabled: bool) -> Self {
self.table.table_border = enabled;
self
}
pub fn cell_spacing(mut self, spacing: f64) -> Self {
self.table.cell_spacing = spacing;
self
}
pub fn total_width(mut self, width: f64) -> Self {
self.table.total_width = Some(width);
self
}
pub fn repeat_headers(mut self, repeat: bool) -> Self {
self.table.repeat_headers = repeat;
self
}
pub fn set_cell_style(mut self, row: usize, col: usize, style: CellStyle) -> Self {
self.table.cell_styles.insert((row, col), style);
self
}
pub fn add_data(mut self, data: Vec<Vec<&str>>) -> Self {
for row in data {
self = self.add_row(row);
}
self
}
pub fn financial_table(self) -> Self {
self.header_style(
CellStyle::header()
.background_color(Color::rgb(0.2, 0.4, 0.8))
.text_color(Color::white()),
)
.default_style(CellStyle::data())
.zebra_striping(Color::rgb(0.97, 0.97, 0.97))
.table_border(true)
}
pub fn minimal_table(self) -> Self {
self.header_style(
CellStyle::new()
.font_size(12.0)
.background_color(Color::rgb(0.95, 0.95, 0.95)),
)
.default_style(CellStyle::data())
.table_border(false)
.cell_spacing(2.0)
}
}
impl Default for AdvancedTableBuilder {
fn default() -> Self {
Self::new()
}
}
impl AdvancedTable {
pub fn calculate_width(&self) -> f64 {
if let Some(width) = self.total_width {
width
} else {
self.columns.iter().map(|col| col.width).sum()
}
}
pub fn row_count(&self) -> usize {
self.rows.len()
}
pub fn column_count(&self) -> usize {
self.columns.len()
}
pub fn get_cell_style(&self, row: usize, col: usize) -> CellStyle {
if let Some(cell_style) = self.cell_styles.get(&(row, col)) {
return cell_style.clone();
}
if let Some(row_data) = self.rows.get(row) {
if let Some(row_style) = &row_data.style {
return row_style.clone();
}
}
if let Some(column) = self.columns.get(col) {
if let Some(column_style) = &column.default_style {
let mut style = column_style.clone();
if let Some(zebra) = &self.zebra_striping {
if let Some(color) = zebra.get_color_for_row(row) {
style.background_color = Some(color);
}
}
return style;
}
}
let mut style = self.default_style.clone();
if let Some(zebra) = &self.zebra_striping {
if let Some(color) = zebra.get_color_for_row(row) {
style.background_color = Some(color);
}
}
style
}
pub fn get_cell_style_ref(&self, row: usize, col: usize) -> &CellStyle {
if let Some(cell_style) = self.cell_styles.get(&(row, col)) {
return cell_style;
}
if let Some(row_data) = self.rows.get(row) {
if let Some(row_style) = &row_data.style {
return row_style;
}
}
if let Some(column) = self.columns.get(col) {
if let Some(column_style) = &column.default_style {
return column_style;
}
}
&self.default_style
}
pub fn validate(&self) -> Result<(), TableError> {
let expected_cols = self.columns.len();
let mut rowspan_end: Vec<usize> = vec![0; expected_cols];
for (row_idx, row) in self.rows.iter().enumerate() {
let occupied_by_rowspan: usize =
rowspan_end.iter().filter(|&&end| end > row_idx).count();
let total_colspan: usize = row.cells.iter().map(|c| c.colspan).sum();
if total_colspan + occupied_by_rowspan != expected_cols {
return Err(TableError::ColumnMismatch {
row: row_idx,
found: total_colspan + occupied_by_rowspan,
expected: expected_cols,
});
}
let mut actual_col = 0;
for cell in &row.cells {
while actual_col < expected_cols && rowspan_end[actual_col] > row_idx {
actual_col += 1;
}
if cell.rowspan > 1 {
for c in actual_col..(actual_col + cell.colspan).min(expected_cols) {
rowspan_end[c] = row_idx + cell.rowspan;
}
}
actual_col += cell.colspan;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_column_new() {
let col = Column::new("Header", 100.0);
assert_eq!(col.header, "Header");
assert_eq!(col.width, 100.0);
assert!(col.default_style.is_none());
assert!(!col.auto_resize);
assert!(col.min_width.is_none());
assert!(col.max_width.is_none());
}
#[test]
fn test_column_with_style() {
let style = CellStyle::data();
let col = Column::new("Header", 100.0).with_style(style.clone());
assert!(col.default_style.is_some());
assert_eq!(col.default_style.unwrap().font_size, style.font_size);
}
#[test]
fn test_column_auto_resize() {
let col = Column::new("Header", 100.0).auto_resize(Some(50.0), Some(200.0));
assert!(col.auto_resize);
assert_eq!(col.min_width, Some(50.0));
assert_eq!(col.max_width, Some(200.0));
}
#[test]
fn test_column_auto_resize_no_limits() {
let col = Column::new("Header", 100.0).auto_resize(None, None);
assert!(col.auto_resize);
assert!(col.min_width.is_none());
assert!(col.max_width.is_none());
}
#[test]
fn test_cell_data_new() {
let cell = CellData::new("Content");
assert_eq!(cell.content, "Content");
assert!(cell.style.is_none());
assert_eq!(cell.colspan, 1);
assert_eq!(cell.rowspan, 1);
}
#[test]
fn test_cell_data_with_style() {
let style = CellStyle::header();
let cell = CellData::new("Content").with_style(style);
assert!(cell.style.is_some());
}
#[test]
fn test_cell_data_colspan() {
let cell = CellData::new("Content").colspan(3);
assert_eq!(cell.colspan, 3);
}
#[test]
fn test_cell_data_colspan_min_is_one() {
let cell = CellData::new("Content").colspan(0);
assert_eq!(cell.colspan, 1);
}
#[test]
fn test_cell_data_rowspan() {
let cell = CellData::new("Content").rowspan(2);
assert_eq!(cell.rowspan, 2);
}
#[test]
fn test_cell_data_rowspan_min_is_one() {
let cell = CellData::new("Content").rowspan(0);
assert_eq!(cell.rowspan, 1);
}
#[test]
fn test_cell_data_combined_span() {
let cell = CellData::new("Merged").colspan(2).rowspan(3);
assert_eq!(cell.colspan, 2);
assert_eq!(cell.rowspan, 3);
}
#[test]
fn test_row_data_from_strings() {
let row = RowData::from_strings(vec!["A", "B", "C"]);
assert_eq!(row.cells.len(), 3);
assert_eq!(row.cells[0].content, "A");
assert_eq!(row.cells[1].content, "B");
assert_eq!(row.cells[2].content, "C");
assert!(row.style.is_none());
assert!(row.min_height.is_none());
}
#[test]
fn test_row_data_from_cells() {
let cells = vec![CellData::new("Cell1"), CellData::new("Cell2").colspan(2)];
let row = RowData::from_cells(cells);
assert_eq!(row.cells.len(), 2);
assert_eq!(row.cells[1].colspan, 2);
}
#[test]
fn test_row_data_with_style() {
let style = CellStyle::header();
let row = RowData::from_strings(vec!["A"]).with_style(style);
assert!(row.style.is_some());
}
#[test]
fn test_row_data_min_height() {
let row = RowData::from_strings(vec!["A"]).min_height(50.0);
assert_eq!(row.min_height, Some(50.0));
}
#[test]
fn test_zebra_config_new() {
let odd = Color::rgb(0.9, 0.9, 0.9);
let even = Color::rgb(1.0, 1.0, 1.0);
let config = ZebraConfig::new(Some(odd), Some(even));
assert!(config.odd_color.is_some());
assert!(config.even_color.is_some());
assert!(config.start_with_odd);
}
#[test]
fn test_zebra_config_simple() {
let color = Color::rgb(0.95, 0.95, 0.95);
let config = ZebraConfig::simple(color);
assert!(config.odd_color.is_some());
assert!(config.even_color.is_none());
}
#[test]
fn test_zebra_config_get_color_for_row() {
let odd_color = Color::rgb(0.9, 0.9, 0.9);
let config = ZebraConfig::simple(odd_color);
assert!(config.get_color_for_row(0).is_none()); assert!(config.get_color_for_row(1).is_some()); assert!(config.get_color_for_row(2).is_none()); assert!(config.get_color_for_row(3).is_some()); }
#[test]
fn test_zebra_config_alternating() {
let odd = Color::rgb(0.9, 0.9, 0.9);
let even = Color::rgb(0.95, 0.95, 0.95);
let config = ZebraConfig::new(Some(odd), Some(even));
assert!(config.get_color_for_row(0).is_some()); assert!(config.get_color_for_row(1).is_some()); }
#[test]
fn test_builder_new() {
let builder = AdvancedTableBuilder::new();
let table = builder.add_column("Col1", 100.0).build().unwrap();
assert_eq!(table.columns.len(), 1);
assert!(table.rows.is_empty());
}
#[test]
fn test_builder_default() {
let builder = AdvancedTableBuilder::default();
assert!(builder.table.columns.is_empty());
}
#[test]
fn test_builder_add_column() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 75.0)
.build()
.unwrap();
assert_eq!(table.columns.len(), 2);
assert_eq!(table.columns[0].width, 50.0);
assert_eq!(table.columns[1].width, 75.0);
}
#[test]
fn test_builder_add_styled_column() {
let style = CellStyle::header();
let table = AdvancedTableBuilder::new()
.add_styled_column("Header", 100.0, style)
.build()
.unwrap();
assert!(table.columns[0].default_style.is_some());
}
#[test]
fn test_builder_columns_equal_width() {
let table = AdvancedTableBuilder::new()
.columns_equal_width(vec!["A", "B", "C", "D"], 400.0)
.build()
.unwrap();
assert_eq!(table.columns.len(), 4);
assert_eq!(table.columns[0].width, 100.0);
assert_eq!(table.total_width, Some(400.0));
}
#[test]
fn test_builder_add_row() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["Value"])
.build()
.unwrap();
assert_eq!(table.rows.len(), 1);
assert_eq!(table.rows[0].cells[0].content, "Value");
}
#[test]
fn test_builder_add_row_with_min_height() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row_with_min_height(vec!["Value"], 30.0)
.build()
.unwrap();
assert_eq!(table.rows[0].min_height, Some(30.0));
}
#[test]
fn test_builder_add_row_cells() {
let cells = vec![CellData::new("Cell1").colspan(2), CellData::new("Cell2")];
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 50.0)
.add_column("C", 50.0)
.add_row_cells(cells)
.build()
.unwrap();
assert_eq!(table.rows[0].cells[0].colspan, 2);
}
#[test]
fn test_builder_add_styled_row() {
let style = CellStyle::header();
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_styled_row(vec!["Value"], style)
.build()
.unwrap();
assert!(table.rows[0].style.is_some());
}
#[test]
fn test_builder_default_style() {
let style = CellStyle::new().font_size(14.0);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.default_style(style.clone())
.build()
.unwrap();
assert_eq!(table.default_style.font_size, Some(14.0));
}
#[test]
fn test_builder_data_style() {
let style = CellStyle::new().font_size(16.0);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.data_style(style)
.build()
.unwrap();
assert_eq!(table.default_style.font_size, Some(16.0));
}
#[test]
fn test_builder_header_style() {
let style = CellStyle::new().font_size(18.0);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.header_style(style)
.build()
.unwrap();
assert_eq!(table.header_style.font_size, Some(18.0));
}
#[test]
fn test_builder_show_header() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.show_header(false)
.build()
.unwrap();
assert!(!table.show_header);
}
#[test]
fn test_builder_title() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.title("My Table")
.build()
.unwrap();
assert_eq!(table.title, Some("My Table".to_string()));
}
#[test]
fn test_builder_columns() {
let table = AdvancedTableBuilder::new()
.columns(vec![("X", 30.0), ("Y", 40.0)])
.build()
.unwrap();
assert_eq!(table.columns.len(), 2);
assert_eq!(table.columns[0].header, "X");
assert_eq!(table.columns[1].header, "Y");
}
#[test]
fn test_builder_position() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.position(100.0, 200.0)
.build()
.unwrap();
assert_eq!(table.x, 100.0);
assert_eq!(table.y, 200.0);
}
#[test]
fn test_builder_zebra_stripes() {
let color = Color::rgb(0.95, 0.95, 0.95);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.zebra_stripes(true, color)
.build()
.unwrap();
assert!(table.zebra_striping.is_some());
let zebra = table.zebra_striping.as_ref().unwrap();
assert_eq!(zebra.odd_color, Some(color));
}
#[test]
fn test_builder_zebra_stripes_disabled() {
let color = Color::rgb(0.95, 0.95, 0.95);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.zebra_stripes(false, color)
.build()
.unwrap();
assert!(table.zebra_striping.is_none());
}
#[test]
fn test_builder_zebra_striping() {
let color = Color::rgb(0.9, 0.9, 0.9);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.zebra_striping(color)
.build()
.unwrap();
assert!(table.zebra_striping.is_some());
}
#[test]
fn test_builder_zebra_striping_custom() {
let config = ZebraConfig::new(
Some(Color::rgb(0.9, 0.9, 0.9)),
Some(Color::rgb(1.0, 1.0, 1.0)),
);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.zebra_striping_custom(config)
.build()
.unwrap();
assert!(table.zebra_striping.is_some());
}
#[test]
fn test_builder_add_row_with_style() {
let style = CellStyle::data();
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row_with_style(vec!["Value"], style)
.build()
.unwrap();
assert!(table.rows[0].style.is_some());
}
#[test]
fn test_builder_add_row_with_mixed_styles() {
let style1 = CellStyle::header();
let style2 = CellStyle::data();
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 50.0)
.add_row_with_mixed_styles(vec![(style1, "Header"), (style2, "Data")])
.build()
.unwrap();
assert!(table.rows[0].cells[0].style.is_some());
assert!(table.rows[0].cells[1].style.is_some());
}
#[test]
fn test_builder_table_border() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.table_border(false)
.build()
.unwrap();
assert!(!table.table_border);
}
#[test]
fn test_builder_cell_spacing() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.cell_spacing(5.0)
.build()
.unwrap();
assert_eq!(table.cell_spacing, 5.0);
}
#[test]
fn test_builder_total_width() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.total_width(500.0)
.build()
.unwrap();
assert_eq!(table.total_width, Some(500.0));
}
#[test]
fn test_builder_repeat_headers() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.repeat_headers(true)
.build()
.unwrap();
assert!(table.repeat_headers);
}
#[test]
fn test_builder_set_cell_style() {
let style = CellStyle::header();
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["Value"])
.set_cell_style(0, 0, style)
.build()
.unwrap();
assert!(table.cell_styles.contains_key(&(0, 0)));
}
#[test]
fn test_builder_add_data() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 50.0)
.add_data(vec![vec!["A1", "B1"], vec!["A2", "B2"], vec!["A3", "B3"]])
.build()
.unwrap();
assert_eq!(table.rows.len(), 3);
}
#[test]
fn test_builder_financial_table() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.financial_table()
.build()
.unwrap();
assert!(table.zebra_striping.is_some());
assert!(table.table_border);
}
#[test]
fn test_builder_minimal_table() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.minimal_table()
.build()
.unwrap();
assert!(!table.table_border);
assert_eq!(table.cell_spacing, 2.0);
}
#[test]
fn test_builder_build_fails_without_columns() {
let result = AdvancedTableBuilder::new().build();
assert!(result.is_err());
match result {
Err(TableError::NoColumns) => {}
_ => panic!("Expected NoColumns error"),
}
}
#[test]
fn test_table_calculate_width_explicit() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 75.0)
.total_width(300.0)
.build()
.unwrap();
assert_eq!(table.calculate_width(), 300.0);
}
#[test]
fn test_table_calculate_width_from_columns() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 75.0)
.build()
.unwrap();
assert_eq!(table.calculate_width(), 125.0);
}
#[test]
fn test_table_row_count() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["1"])
.add_row(vec!["2"])
.add_row(vec!["3"])
.build()
.unwrap();
assert_eq!(table.row_count(), 3);
}
#[test]
fn test_table_column_count() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 50.0)
.build()
.unwrap();
assert_eq!(table.column_count(), 2);
}
#[test]
fn test_table_get_cell_style_specific() {
let specific_style = CellStyle::header();
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["Value"])
.set_cell_style(0, 0, specific_style.clone())
.build()
.unwrap();
let style = table.get_cell_style(0, 0);
assert_eq!(style.font_size, specific_style.font_size);
}
#[test]
fn test_table_get_cell_style_row() {
let row_style = CellStyle::header();
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_styled_row(vec!["Value"], row_style.clone())
.build()
.unwrap();
let style = table.get_cell_style(0, 0);
assert_eq!(style.font_size, row_style.font_size);
}
#[test]
fn test_table_get_cell_style_column() {
let col_style = CellStyle::new().font_size(20.0);
let table = AdvancedTableBuilder::new()
.add_styled_column("A", 50.0, col_style.clone())
.add_row(vec!["Value"])
.build()
.unwrap();
let style = table.get_cell_style(0, 0);
assert_eq!(style.font_size, Some(20.0));
}
#[test]
fn test_table_get_cell_style_zebra() {
let zebra_color = Color::rgb(0.9, 0.9, 0.9);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["Row0"])
.add_row(vec!["Row1"])
.zebra_striping(zebra_color)
.build()
.unwrap();
let style_row1 = table.get_cell_style(1, 0);
assert!(style_row1.background_color.is_some());
}
#[test]
fn test_table_get_cell_style_column_with_zebra() {
let col_style = CellStyle::new().font_size(20.0);
let zebra_color = Color::rgb(0.9, 0.9, 0.9);
let table = AdvancedTableBuilder::new()
.add_styled_column("A", 50.0, col_style)
.add_row(vec!["Row0"])
.add_row(vec!["Row1"])
.zebra_striping(zebra_color)
.build()
.unwrap();
let style = table.get_cell_style(1, 0);
assert_eq!(style.font_size, Some(20.0));
assert!(style.background_color.is_some());
}
#[test]
fn test_table_validate_success() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 50.0)
.add_row(vec!["1", "2"])
.add_row(vec!["3", "4"])
.build()
.unwrap();
assert!(table.validate().is_ok());
}
#[test]
fn test_table_validate_column_mismatch() {
let mut table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_column("B", 50.0)
.build()
.unwrap();
table.rows.push(RowData::from_strings(vec!["1", "2", "3"]));
let result = table.validate();
assert!(result.is_err());
match result {
Err(TableError::ColumnMismatch {
row,
found,
expected,
}) => {
assert_eq!(row, 0);
assert_eq!(found, 3);
assert_eq!(expected, 2);
}
_ => panic!("Expected ColumnMismatch error"),
}
}
#[test]
fn test_table_get_cell_style_default() {
let default_style = CellStyle::new().font_size(12.0);
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["Value"])
.default_style(default_style.clone())
.build()
.unwrap();
let style = table.get_cell_style(0, 0);
assert_eq!(style.font_size, Some(12.0));
}
#[test]
fn test_table_get_cell_style_invalid_row() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["Value"])
.build()
.unwrap();
let style = table.get_cell_style(100, 0);
assert_eq!(style.font_size, table.default_style.font_size);
}
#[test]
fn test_table_get_cell_style_invalid_column() {
let table = AdvancedTableBuilder::new()
.add_column("A", 50.0)
.add_row(vec!["Value"])
.build()
.unwrap();
let style = table.get_cell_style(0, 100);
assert_eq!(style.font_size, table.default_style.font_size);
}
#[test]
fn test_get_cell_style_ref_returns_correct_style() {
use crate::graphics::Color;
let cell_style = CellStyle::new().font_size(18.0).text_color(Color::red());
let table = AdvancedTableBuilder::new()
.add_column("A", 100.0)
.add_row(vec!["Value"])
.set_cell_style(0, 0, cell_style.clone())
.build()
.unwrap();
let style_ref = table.get_cell_style_ref(0, 0);
assert!(
(style_ref.font_size.unwrap_or(12.0) - 18.0).abs() < f64::EPSILON,
"Cell-specific style should have font_size 18.0"
);
let table_default = AdvancedTableBuilder::new()
.add_column("A", 100.0)
.add_row(vec!["Value"])
.build()
.unwrap();
let style_ref_default = table_default.get_cell_style_ref(0, 0);
assert_eq!(
style_ref_default.font_size, table_default.default_style.font_size,
"Should fall back to default_style when no overrides are set"
);
let style_owned = table.get_cell_style(0, 0);
let style_ref2 = table.get_cell_style_ref(0, 0);
assert_eq!(
style_owned.font_size, style_ref2.font_size,
"get_cell_style and get_cell_style_ref should agree when no zebra striping"
);
}
}