1use super::cell_style::CellStyle;
4use super::error::TableError;
5use super::header_builder::HeaderBuilder;
6use crate::graphics::Color;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub struct Column {
12 pub header: String,
14 pub width: f64,
16 pub default_style: Option<CellStyle>,
18 pub auto_resize: bool,
20 pub min_width: Option<f64>,
22 pub max_width: Option<f64>,
24}
25
26impl Column {
27 pub fn new<S: Into<String>>(header: S, width: f64) -> Self {
29 Self {
30 header: header.into(),
31 width,
32 default_style: None,
33 auto_resize: false,
34 min_width: None,
35 max_width: None,
36 }
37 }
38
39 pub fn with_style(mut self, style: CellStyle) -> Self {
41 self.default_style = Some(style);
42 self
43 }
44
45 pub fn auto_resize(mut self, min_width: Option<f64>, max_width: Option<f64>) -> Self {
47 self.auto_resize = true;
48 self.min_width = min_width;
49 self.max_width = max_width;
50 self
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct CellData {
57 pub content: String,
59 pub style: Option<CellStyle>,
61 pub colspan: usize,
63 pub rowspan: usize,
65}
66
67impl CellData {
68 pub fn new<S: Into<String>>(content: S) -> Self {
70 Self {
71 content: content.into(),
72 style: None,
73 colspan: 1,
74 rowspan: 1,
75 }
76 }
77
78 pub fn with_style(mut self, style: CellStyle) -> Self {
80 self.style = Some(style);
81 self
82 }
83
84 pub fn colspan(mut self, span: usize) -> Self {
86 self.colspan = span.max(1);
87 self
88 }
89
90 pub fn rowspan(mut self, span: usize) -> Self {
92 self.rowspan = span.max(1);
93 self
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct RowData {
100 pub cells: Vec<CellData>,
102 pub style: Option<CellStyle>,
104 pub min_height: Option<f64>,
106}
107
108impl RowData {
109 pub fn from_strings(content: Vec<&str>) -> Self {
111 let cells = content.into_iter().map(CellData::new).collect();
112
113 Self {
114 cells,
115 style: None,
116 min_height: None,
117 }
118 }
119
120 pub fn from_cells(cells: Vec<CellData>) -> Self {
122 Self {
123 cells,
124 style: None,
125 min_height: None,
126 }
127 }
128
129 pub fn with_style(mut self, style: CellStyle) -> Self {
131 self.style = Some(style);
132 self
133 }
134
135 pub fn min_height(mut self, height: f64) -> Self {
137 self.min_height = Some(height);
138 self
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct AdvancedTable {
145 pub title: Option<String>,
147 pub x: f64,
149 pub y: f64,
151 pub columns: Vec<Column>,
153 pub rows: Vec<RowData>,
155 pub header: Option<HeaderBuilder>,
157 pub show_header: bool,
159 pub default_style: CellStyle,
161 pub header_style: CellStyle,
163 pub zebra_striping: Option<ZebraConfig>,
165 pub table_border: bool,
167 pub cell_spacing: f64,
169 pub total_width: Option<f64>,
171 pub repeat_headers: bool,
173 pub cell_styles: HashMap<(usize, usize), CellStyle>,
175}
176
177#[derive(Debug, Clone)]
179pub struct ZebraConfig {
180 pub odd_color: Option<Color>,
182 pub even_color: Option<Color>,
184 pub start_with_odd: bool,
186}
187
188impl ZebraConfig {
189 pub fn new(odd_color: Option<Color>, even_color: Option<Color>) -> Self {
191 Self {
192 odd_color,
193 even_color,
194 start_with_odd: true,
195 }
196 }
197
198 pub fn simple(color: Color) -> Self {
200 Self::new(Some(color), None)
201 }
202
203 pub fn get_color_for_row(&self, row_index: usize) -> Option<Color> {
205 let is_odd = (row_index % 2) == (if self.start_with_odd { 1 } else { 0 });
206 if is_odd {
207 self.odd_color
208 } else {
209 self.even_color
210 }
211 }
212}
213
214pub struct AdvancedTableBuilder {
216 table: AdvancedTable,
217}
218
219impl AdvancedTableBuilder {
220 pub fn new() -> Self {
222 Self {
223 table: AdvancedTable {
224 title: None,
225 x: 0.0,
226 y: 0.0,
227 columns: Vec::new(),
228 rows: Vec::new(),
229 header: None,
230 show_header: true,
231 default_style: CellStyle::data(),
232 header_style: CellStyle::header(),
233 zebra_striping: None,
234 table_border: true,
235 cell_spacing: 0.0,
236 total_width: None,
237 repeat_headers: false,
238 cell_styles: HashMap::new(),
239 },
240 }
241 }
242
243 pub fn add_column<S: Into<String>>(mut self, header: S, width: f64) -> Self {
245 self.table.columns.push(Column::new(header, width));
246 self
247 }
248
249 pub fn add_styled_column<S: Into<String>>(
251 mut self,
252 header: S,
253 width: f64,
254 style: CellStyle,
255 ) -> Self {
256 self.table
257 .columns
258 .push(Column::new(header, width).with_style(style));
259 self
260 }
261
262 pub fn columns_equal_width(mut self, headers: Vec<&str>, total_width: f64) -> Self {
264 let column_width = total_width / headers.len() as f64;
265 self.table.columns = headers
266 .into_iter()
267 .map(|header| Column::new(header, column_width))
268 .collect();
269 self.table.total_width = Some(total_width);
270 self
271 }
272
273 pub fn add_row(mut self, content: Vec<&str>) -> Self {
275 self.table.rows.push(RowData::from_strings(content));
276 self
277 }
278
279 pub fn add_row_with_min_height(mut self, content: Vec<&str>, min_height: f64) -> Self {
280 self.table
281 .rows
282 .push(RowData::from_strings(content).min_height(min_height));
283 self
284 }
285
286 pub fn add_row_cells(mut self, cells: Vec<CellData>) -> Self {
288 self.table.rows.push(RowData::from_cells(cells));
289 self
290 }
291
292 pub fn add_styled_row(mut self, content: Vec<&str>, style: CellStyle) -> Self {
294 self.table
295 .rows
296 .push(RowData::from_strings(content).with_style(style));
297 self
298 }
299
300 pub fn default_style(mut self, style: CellStyle) -> Self {
302 self.table.default_style = style;
303 self
304 }
305
306 pub fn data_style(mut self, style: CellStyle) -> Self {
308 self.table.default_style = style;
309 self
310 }
311
312 pub fn header_style(mut self, style: CellStyle) -> Self {
314 self.table.header_style = style;
315 self
316 }
317
318 pub fn show_header(mut self, show: bool) -> Self {
320 self.table.show_header = show;
321 self
322 }
323
324 pub fn title<S: Into<String>>(mut self, title: S) -> Self {
326 self.table.title = Some(title.into());
327 self
328 }
329
330 pub fn columns(mut self, column_specs: Vec<(&str, f64)>) -> Self {
332 self.table.columns = column_specs
333 .into_iter()
334 .map(|(header, width)| Column::new(header, width))
335 .collect();
336 self
337 }
338
339 pub fn position(mut self, x: f64, y: f64) -> Self {
341 self.table.x = x;
342 self.table.y = y;
343 self
344 }
345
346 pub fn complex_header(mut self, header: HeaderBuilder) -> Self {
348 if self.table.columns.is_empty() {
350 let column_count = header.total_columns;
351 for i in 0..column_count {
352 self.table.columns.push(Column::new(
353 format!("Column {}", i + 1),
354 100.0, ));
356 }
357 }
358 self.table.header = Some(header);
359 self
360 }
361
362 pub fn zebra_stripes(mut self, enabled: bool, color: Color) -> Self {
364 if enabled {
365 self.table.zebra_striping = Some(ZebraConfig::simple(color));
366 } else {
367 self.table.zebra_striping = None;
368 }
369 self
370 }
371
372 pub fn add_row_with_style(mut self, content: Vec<&str>, style: CellStyle) -> Self {
374 let mut row = RowData::from_strings(content);
375 row = row.with_style(style);
376 self.table.rows.push(row);
377 self
378 }
379
380 pub fn add_row_with_mixed_styles(mut self, cells: Vec<(CellStyle, &str)>) -> Self {
382 let cell_data: Vec<CellData> = cells
383 .into_iter()
384 .map(|(style, content)| CellData::new(content.to_string()).with_style(style))
385 .collect();
386 self.table.rows.push(RowData::from_cells(cell_data));
387 self
388 }
389
390 pub fn build(self) -> Result<AdvancedTable, TableError> {
392 if self.table.columns.is_empty() {
393 return Err(TableError::NoColumns);
394 }
395 Ok(self.table)
396 }
397
398 pub fn zebra_striping(mut self, color: Color) -> Self {
400 self.table.zebra_striping = Some(ZebraConfig::simple(color));
401 self
402 }
403
404 pub fn zebra_striping_custom(mut self, config: ZebraConfig) -> Self {
406 self.table.zebra_striping = Some(config);
407 self
408 }
409
410 pub fn table_border(mut self, enabled: bool) -> Self {
412 self.table.table_border = enabled;
413 self
414 }
415
416 pub fn cell_spacing(mut self, spacing: f64) -> Self {
418 self.table.cell_spacing = spacing;
419 self
420 }
421
422 pub fn total_width(mut self, width: f64) -> Self {
424 self.table.total_width = Some(width);
425 self
426 }
427
428 pub fn repeat_headers(mut self, repeat: bool) -> Self {
430 self.table.repeat_headers = repeat;
431 self
432 }
433
434 pub fn set_cell_style(mut self, row: usize, col: usize, style: CellStyle) -> Self {
436 self.table.cell_styles.insert((row, col), style);
437 self
438 }
439
440 pub fn add_data(mut self, data: Vec<Vec<&str>>) -> Self {
442 for row in data {
443 self = self.add_row(row);
444 }
445 self
446 }
447
448 pub fn financial_table(self) -> Self {
450 self.header_style(
451 CellStyle::header()
452 .background_color(Color::rgb(0.2, 0.4, 0.8))
453 .text_color(Color::white()),
454 )
455 .default_style(CellStyle::data())
456 .zebra_striping(Color::rgb(0.97, 0.97, 0.97))
457 .table_border(true)
458 }
459
460 pub fn minimal_table(self) -> Self {
462 self.header_style(
463 CellStyle::new()
464 .font_size(12.0)
465 .background_color(Color::rgb(0.95, 0.95, 0.95)),
466 )
467 .default_style(CellStyle::data())
468 .table_border(false)
469 .cell_spacing(2.0)
470 }
471}
472
473impl Default for AdvancedTableBuilder {
474 fn default() -> Self {
475 Self::new()
476 }
477}
478
479impl AdvancedTable {
480 pub fn calculate_width(&self) -> f64 {
482 if let Some(width) = self.total_width {
483 width
484 } else {
485 self.columns.iter().map(|col| col.width).sum()
486 }
487 }
488
489 pub fn row_count(&self) -> usize {
491 self.rows.len()
492 }
493
494 pub fn column_count(&self) -> usize {
496 self.columns.len()
497 }
498
499 pub fn get_cell_style(&self, row: usize, col: usize) -> CellStyle {
501 if let Some(cell_style) = self.cell_styles.get(&(row, col)) {
505 return cell_style.clone();
506 }
507
508 if let Some(row_data) = self.rows.get(row) {
510 if let Some(row_style) = &row_data.style {
511 return row_style.clone();
512 }
513 }
514
515 if let Some(column) = self.columns.get(col) {
517 if let Some(column_style) = &column.default_style {
518 let mut style = column_style.clone();
519
520 if let Some(zebra) = &self.zebra_striping {
522 if let Some(color) = zebra.get_color_for_row(row) {
523 style.background_color = Some(color);
524 }
525 }
526
527 return style;
528 }
529 }
530
531 let mut style = self.default_style.clone();
533 if let Some(zebra) = &self.zebra_striping {
534 if let Some(color) = zebra.get_color_for_row(row) {
535 style.background_color = Some(color);
536 }
537 }
538
539 style
540 }
541
542 pub fn get_cell_style_ref(&self, row: usize, col: usize) -> &CellStyle {
548 if let Some(cell_style) = self.cell_styles.get(&(row, col)) {
551 return cell_style;
552 }
553
554 if let Some(row_data) = self.rows.get(row) {
555 if let Some(row_style) = &row_data.style {
556 return row_style;
557 }
558 }
559
560 if let Some(column) = self.columns.get(col) {
561 if let Some(column_style) = &column.default_style {
562 return column_style;
565 }
566 }
567
568 &self.default_style
569 }
570
571 pub fn validate(&self) -> Result<(), TableError> {
573 let expected_cols = self.columns.len();
574 let mut rowspan_end: Vec<usize> = vec![0; expected_cols];
576
577 for (row_idx, row) in self.rows.iter().enumerate() {
578 let occupied_by_rowspan: usize =
580 rowspan_end.iter().filter(|&&end| end > row_idx).count();
581
582 let total_colspan: usize = row.cells.iter().map(|c| c.colspan).sum();
584 if total_colspan + occupied_by_rowspan != expected_cols {
585 return Err(TableError::ColumnMismatch {
586 row: row_idx,
587 found: total_colspan + occupied_by_rowspan,
588 expected: expected_cols,
589 });
590 }
591
592 let mut actual_col = 0;
594 for cell in &row.cells {
595 while actual_col < expected_cols && rowspan_end[actual_col] > row_idx {
597 actual_col += 1;
598 }
599 if cell.rowspan > 1 {
600 for c in actual_col..(actual_col + cell.colspan).min(expected_cols) {
601 rowspan_end[c] = row_idx + cell.rowspan;
602 }
603 }
604 actual_col += cell.colspan;
605 }
606 }
607
608 Ok(())
609 }
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
621 fn test_column_new() {
622 let col = Column::new("Header", 100.0);
623 assert_eq!(col.header, "Header");
624 assert_eq!(col.width, 100.0);
625 assert!(col.default_style.is_none());
626 assert!(!col.auto_resize);
627 assert!(col.min_width.is_none());
628 assert!(col.max_width.is_none());
629 }
630
631 #[test]
632 fn test_column_with_style() {
633 let style = CellStyle::data();
634 let col = Column::new("Header", 100.0).with_style(style.clone());
635 assert!(col.default_style.is_some());
636 assert_eq!(col.default_style.unwrap().font_size, style.font_size);
638 }
639
640 #[test]
641 fn test_column_auto_resize() {
642 let col = Column::new("Header", 100.0).auto_resize(Some(50.0), Some(200.0));
643 assert!(col.auto_resize);
644 assert_eq!(col.min_width, Some(50.0));
645 assert_eq!(col.max_width, Some(200.0));
646 }
647
648 #[test]
649 fn test_column_auto_resize_no_limits() {
650 let col = Column::new("Header", 100.0).auto_resize(None, None);
651 assert!(col.auto_resize);
652 assert!(col.min_width.is_none());
653 assert!(col.max_width.is_none());
654 }
655
656 #[test]
661 fn test_cell_data_new() {
662 let cell = CellData::new("Content");
663 assert_eq!(cell.content, "Content");
664 assert!(cell.style.is_none());
665 assert_eq!(cell.colspan, 1);
666 assert_eq!(cell.rowspan, 1);
667 }
668
669 #[test]
670 fn test_cell_data_with_style() {
671 let style = CellStyle::header();
672 let cell = CellData::new("Content").with_style(style);
673 assert!(cell.style.is_some());
674 }
675
676 #[test]
677 fn test_cell_data_colspan() {
678 let cell = CellData::new("Content").colspan(3);
679 assert_eq!(cell.colspan, 3);
680 }
681
682 #[test]
683 fn test_cell_data_colspan_min_is_one() {
684 let cell = CellData::new("Content").colspan(0);
686 assert_eq!(cell.colspan, 1);
687 }
688
689 #[test]
690 fn test_cell_data_rowspan() {
691 let cell = CellData::new("Content").rowspan(2);
692 assert_eq!(cell.rowspan, 2);
693 }
694
695 #[test]
696 fn test_cell_data_rowspan_min_is_one() {
697 let cell = CellData::new("Content").rowspan(0);
699 assert_eq!(cell.rowspan, 1);
700 }
701
702 #[test]
703 fn test_cell_data_combined_span() {
704 let cell = CellData::new("Merged").colspan(2).rowspan(3);
705 assert_eq!(cell.colspan, 2);
706 assert_eq!(cell.rowspan, 3);
707 }
708
709 #[test]
714 fn test_row_data_from_strings() {
715 let row = RowData::from_strings(vec!["A", "B", "C"]);
716 assert_eq!(row.cells.len(), 3);
717 assert_eq!(row.cells[0].content, "A");
718 assert_eq!(row.cells[1].content, "B");
719 assert_eq!(row.cells[2].content, "C");
720 assert!(row.style.is_none());
721 assert!(row.min_height.is_none());
722 }
723
724 #[test]
725 fn test_row_data_from_cells() {
726 let cells = vec![CellData::new("Cell1"), CellData::new("Cell2").colspan(2)];
727 let row = RowData::from_cells(cells);
728 assert_eq!(row.cells.len(), 2);
729 assert_eq!(row.cells[1].colspan, 2);
730 }
731
732 #[test]
733 fn test_row_data_with_style() {
734 let style = CellStyle::header();
735 let row = RowData::from_strings(vec!["A"]).with_style(style);
736 assert!(row.style.is_some());
737 }
738
739 #[test]
740 fn test_row_data_min_height() {
741 let row = RowData::from_strings(vec!["A"]).min_height(50.0);
742 assert_eq!(row.min_height, Some(50.0));
743 }
744
745 #[test]
750 fn test_zebra_config_new() {
751 let odd = Color::rgb(0.9, 0.9, 0.9);
752 let even = Color::rgb(1.0, 1.0, 1.0);
753 let config = ZebraConfig::new(Some(odd), Some(even));
754 assert!(config.odd_color.is_some());
755 assert!(config.even_color.is_some());
756 assert!(config.start_with_odd);
757 }
758
759 #[test]
760 fn test_zebra_config_simple() {
761 let color = Color::rgb(0.95, 0.95, 0.95);
762 let config = ZebraConfig::simple(color);
763 assert!(config.odd_color.is_some());
764 assert!(config.even_color.is_none());
765 }
766
767 #[test]
768 fn test_zebra_config_get_color_for_row() {
769 let odd_color = Color::rgb(0.9, 0.9, 0.9);
770 let config = ZebraConfig::simple(odd_color);
771
772 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()); }
778
779 #[test]
780 fn test_zebra_config_alternating() {
781 let odd = Color::rgb(0.9, 0.9, 0.9);
782 let even = Color::rgb(0.95, 0.95, 0.95);
783 let config = ZebraConfig::new(Some(odd), Some(even));
784
785 assert!(config.get_color_for_row(0).is_some()); assert!(config.get_color_for_row(1).is_some()); }
789
790 #[test]
795 fn test_builder_new() {
796 let builder = AdvancedTableBuilder::new();
797 let table = builder.add_column("Col1", 100.0).build().unwrap();
798 assert_eq!(table.columns.len(), 1);
799 assert!(table.rows.is_empty());
800 }
801
802 #[test]
803 fn test_builder_default() {
804 let builder = AdvancedTableBuilder::default();
805 assert!(builder.table.columns.is_empty());
806 }
807
808 #[test]
809 fn test_builder_add_column() {
810 let table = AdvancedTableBuilder::new()
811 .add_column("A", 50.0)
812 .add_column("B", 75.0)
813 .build()
814 .unwrap();
815 assert_eq!(table.columns.len(), 2);
816 assert_eq!(table.columns[0].width, 50.0);
817 assert_eq!(table.columns[1].width, 75.0);
818 }
819
820 #[test]
821 fn test_builder_add_styled_column() {
822 let style = CellStyle::header();
823 let table = AdvancedTableBuilder::new()
824 .add_styled_column("Header", 100.0, style)
825 .build()
826 .unwrap();
827 assert!(table.columns[0].default_style.is_some());
828 }
829
830 #[test]
831 fn test_builder_columns_equal_width() {
832 let table = AdvancedTableBuilder::new()
833 .columns_equal_width(vec!["A", "B", "C", "D"], 400.0)
834 .build()
835 .unwrap();
836 assert_eq!(table.columns.len(), 4);
837 assert_eq!(table.columns[0].width, 100.0);
838 assert_eq!(table.total_width, Some(400.0));
839 }
840
841 #[test]
842 fn test_builder_add_row() {
843 let table = AdvancedTableBuilder::new()
844 .add_column("A", 50.0)
845 .add_row(vec!["Value"])
846 .build()
847 .unwrap();
848 assert_eq!(table.rows.len(), 1);
849 assert_eq!(table.rows[0].cells[0].content, "Value");
850 }
851
852 #[test]
853 fn test_builder_add_row_with_min_height() {
854 let table = AdvancedTableBuilder::new()
855 .add_column("A", 50.0)
856 .add_row_with_min_height(vec!["Value"], 30.0)
857 .build()
858 .unwrap();
859 assert_eq!(table.rows[0].min_height, Some(30.0));
860 }
861
862 #[test]
863 fn test_builder_add_row_cells() {
864 let cells = vec![CellData::new("Cell1").colspan(2), CellData::new("Cell2")];
865 let table = AdvancedTableBuilder::new()
866 .add_column("A", 50.0)
867 .add_column("B", 50.0)
868 .add_column("C", 50.0)
869 .add_row_cells(cells)
870 .build()
871 .unwrap();
872 assert_eq!(table.rows[0].cells[0].colspan, 2);
873 }
874
875 #[test]
876 fn test_builder_add_styled_row() {
877 let style = CellStyle::header();
878 let table = AdvancedTableBuilder::new()
879 .add_column("A", 50.0)
880 .add_styled_row(vec!["Value"], style)
881 .build()
882 .unwrap();
883 assert!(table.rows[0].style.is_some());
884 }
885
886 #[test]
887 fn test_builder_default_style() {
888 let style = CellStyle::new().font_size(14.0);
889 let table = AdvancedTableBuilder::new()
890 .add_column("A", 50.0)
891 .default_style(style.clone())
892 .build()
893 .unwrap();
894 assert_eq!(table.default_style.font_size, Some(14.0));
895 }
896
897 #[test]
898 fn test_builder_data_style() {
899 let style = CellStyle::new().font_size(16.0);
900 let table = AdvancedTableBuilder::new()
901 .add_column("A", 50.0)
902 .data_style(style)
903 .build()
904 .unwrap();
905 assert_eq!(table.default_style.font_size, Some(16.0));
906 }
907
908 #[test]
909 fn test_builder_header_style() {
910 let style = CellStyle::new().font_size(18.0);
911 let table = AdvancedTableBuilder::new()
912 .add_column("A", 50.0)
913 .header_style(style)
914 .build()
915 .unwrap();
916 assert_eq!(table.header_style.font_size, Some(18.0));
917 }
918
919 #[test]
920 fn test_builder_show_header() {
921 let table = AdvancedTableBuilder::new()
922 .add_column("A", 50.0)
923 .show_header(false)
924 .build()
925 .unwrap();
926 assert!(!table.show_header);
927 }
928
929 #[test]
930 fn test_builder_title() {
931 let table = AdvancedTableBuilder::new()
932 .add_column("A", 50.0)
933 .title("My Table")
934 .build()
935 .unwrap();
936 assert_eq!(table.title, Some("My Table".to_string()));
937 }
938
939 #[test]
940 fn test_builder_columns() {
941 let table = AdvancedTableBuilder::new()
942 .columns(vec![("X", 30.0), ("Y", 40.0)])
943 .build()
944 .unwrap();
945 assert_eq!(table.columns.len(), 2);
946 assert_eq!(table.columns[0].header, "X");
947 assert_eq!(table.columns[1].header, "Y");
948 }
949
950 #[test]
951 fn test_builder_position() {
952 let table = AdvancedTableBuilder::new()
953 .add_column("A", 50.0)
954 .position(100.0, 200.0)
955 .build()
956 .unwrap();
957 assert_eq!(table.x, 100.0);
958 assert_eq!(table.y, 200.0);
959 }
960
961 #[test]
962 fn test_builder_zebra_stripes() {
963 let color = Color::rgb(0.95, 0.95, 0.95);
964 let table = AdvancedTableBuilder::new()
965 .add_column("A", 50.0)
966 .zebra_stripes(true, color)
967 .build()
968 .unwrap();
969 assert!(table.zebra_striping.is_some());
970 let zebra = table.zebra_striping.as_ref().unwrap();
972 assert_eq!(zebra.odd_color, Some(color));
973 }
974
975 #[test]
976 fn test_builder_zebra_stripes_disabled() {
977 let color = Color::rgb(0.95, 0.95, 0.95);
978 let table = AdvancedTableBuilder::new()
979 .add_column("A", 50.0)
980 .zebra_stripes(false, color)
981 .build()
982 .unwrap();
983 assert!(table.zebra_striping.is_none());
984 }
985
986 #[test]
987 fn test_builder_zebra_striping() {
988 let color = Color::rgb(0.9, 0.9, 0.9);
989 let table = AdvancedTableBuilder::new()
990 .add_column("A", 50.0)
991 .zebra_striping(color)
992 .build()
993 .unwrap();
994 assert!(table.zebra_striping.is_some());
995 }
996
997 #[test]
998 fn test_builder_zebra_striping_custom() {
999 let config = ZebraConfig::new(
1000 Some(Color::rgb(0.9, 0.9, 0.9)),
1001 Some(Color::rgb(1.0, 1.0, 1.0)),
1002 );
1003 let table = AdvancedTableBuilder::new()
1004 .add_column("A", 50.0)
1005 .zebra_striping_custom(config)
1006 .build()
1007 .unwrap();
1008 assert!(table.zebra_striping.is_some());
1009 }
1010
1011 #[test]
1012 fn test_builder_add_row_with_style() {
1013 let style = CellStyle::data();
1014 let table = AdvancedTableBuilder::new()
1015 .add_column("A", 50.0)
1016 .add_row_with_style(vec!["Value"], style)
1017 .build()
1018 .unwrap();
1019 assert!(table.rows[0].style.is_some());
1020 }
1021
1022 #[test]
1023 fn test_builder_add_row_with_mixed_styles() {
1024 let style1 = CellStyle::header();
1025 let style2 = CellStyle::data();
1026 let table = AdvancedTableBuilder::new()
1027 .add_column("A", 50.0)
1028 .add_column("B", 50.0)
1029 .add_row_with_mixed_styles(vec![(style1, "Header"), (style2, "Data")])
1030 .build()
1031 .unwrap();
1032 assert!(table.rows[0].cells[0].style.is_some());
1033 assert!(table.rows[0].cells[1].style.is_some());
1034 }
1035
1036 #[test]
1037 fn test_builder_table_border() {
1038 let table = AdvancedTableBuilder::new()
1039 .add_column("A", 50.0)
1040 .table_border(false)
1041 .build()
1042 .unwrap();
1043 assert!(!table.table_border);
1044 }
1045
1046 #[test]
1047 fn test_builder_cell_spacing() {
1048 let table = AdvancedTableBuilder::new()
1049 .add_column("A", 50.0)
1050 .cell_spacing(5.0)
1051 .build()
1052 .unwrap();
1053 assert_eq!(table.cell_spacing, 5.0);
1054 }
1055
1056 #[test]
1057 fn test_builder_total_width() {
1058 let table = AdvancedTableBuilder::new()
1059 .add_column("A", 50.0)
1060 .total_width(500.0)
1061 .build()
1062 .unwrap();
1063 assert_eq!(table.total_width, Some(500.0));
1064 }
1065
1066 #[test]
1067 fn test_builder_repeat_headers() {
1068 let table = AdvancedTableBuilder::new()
1069 .add_column("A", 50.0)
1070 .repeat_headers(true)
1071 .build()
1072 .unwrap();
1073 assert!(table.repeat_headers);
1074 }
1075
1076 #[test]
1077 fn test_builder_set_cell_style() {
1078 let style = CellStyle::header();
1079 let table = AdvancedTableBuilder::new()
1080 .add_column("A", 50.0)
1081 .add_row(vec!["Value"])
1082 .set_cell_style(0, 0, style)
1083 .build()
1084 .unwrap();
1085 assert!(table.cell_styles.contains_key(&(0, 0)));
1086 }
1087
1088 #[test]
1089 fn test_builder_add_data() {
1090 let table = AdvancedTableBuilder::new()
1091 .add_column("A", 50.0)
1092 .add_column("B", 50.0)
1093 .add_data(vec![vec!["A1", "B1"], vec!["A2", "B2"], vec!["A3", "B3"]])
1094 .build()
1095 .unwrap();
1096 assert_eq!(table.rows.len(), 3);
1097 }
1098
1099 #[test]
1100 fn test_builder_financial_table() {
1101 let table = AdvancedTableBuilder::new()
1102 .add_column("A", 50.0)
1103 .financial_table()
1104 .build()
1105 .unwrap();
1106 assert!(table.zebra_striping.is_some());
1108 assert!(table.table_border);
1109 }
1110
1111 #[test]
1112 fn test_builder_minimal_table() {
1113 let table = AdvancedTableBuilder::new()
1114 .add_column("A", 50.0)
1115 .minimal_table()
1116 .build()
1117 .unwrap();
1118 assert!(!table.table_border);
1119 assert_eq!(table.cell_spacing, 2.0);
1120 }
1121
1122 #[test]
1123 fn test_builder_build_fails_without_columns() {
1124 let result = AdvancedTableBuilder::new().build();
1125 assert!(result.is_err());
1126 match result {
1127 Err(TableError::NoColumns) => {}
1128 _ => panic!("Expected NoColumns error"),
1129 }
1130 }
1131
1132 #[test]
1137 fn test_table_calculate_width_explicit() {
1138 let table = AdvancedTableBuilder::new()
1139 .add_column("A", 50.0)
1140 .add_column("B", 75.0)
1141 .total_width(300.0)
1142 .build()
1143 .unwrap();
1144 assert_eq!(table.calculate_width(), 300.0);
1145 }
1146
1147 #[test]
1148 fn test_table_calculate_width_from_columns() {
1149 let table = AdvancedTableBuilder::new()
1150 .add_column("A", 50.0)
1151 .add_column("B", 75.0)
1152 .build()
1153 .unwrap();
1154 assert_eq!(table.calculate_width(), 125.0);
1155 }
1156
1157 #[test]
1158 fn test_table_row_count() {
1159 let table = AdvancedTableBuilder::new()
1160 .add_column("A", 50.0)
1161 .add_row(vec!["1"])
1162 .add_row(vec!["2"])
1163 .add_row(vec!["3"])
1164 .build()
1165 .unwrap();
1166 assert_eq!(table.row_count(), 3);
1167 }
1168
1169 #[test]
1170 fn test_table_column_count() {
1171 let table = AdvancedTableBuilder::new()
1172 .add_column("A", 50.0)
1173 .add_column("B", 50.0)
1174 .build()
1175 .unwrap();
1176 assert_eq!(table.column_count(), 2);
1177 }
1178
1179 #[test]
1180 fn test_table_get_cell_style_specific() {
1181 let specific_style = CellStyle::header();
1182 let table = AdvancedTableBuilder::new()
1183 .add_column("A", 50.0)
1184 .add_row(vec!["Value"])
1185 .set_cell_style(0, 0, specific_style.clone())
1186 .build()
1187 .unwrap();
1188 let style = table.get_cell_style(0, 0);
1189 assert_eq!(style.font_size, specific_style.font_size);
1190 }
1191
1192 #[test]
1193 fn test_table_get_cell_style_row() {
1194 let row_style = CellStyle::header();
1195 let table = AdvancedTableBuilder::new()
1196 .add_column("A", 50.0)
1197 .add_styled_row(vec!["Value"], row_style.clone())
1198 .build()
1199 .unwrap();
1200 let style = table.get_cell_style(0, 0);
1201 assert_eq!(style.font_size, row_style.font_size);
1202 }
1203
1204 #[test]
1205 fn test_table_get_cell_style_column() {
1206 let col_style = CellStyle::new().font_size(20.0);
1207 let table = AdvancedTableBuilder::new()
1208 .add_styled_column("A", 50.0, col_style.clone())
1209 .add_row(vec!["Value"])
1210 .build()
1211 .unwrap();
1212 let style = table.get_cell_style(0, 0);
1213 assert_eq!(style.font_size, Some(20.0));
1214 }
1215
1216 #[test]
1217 fn test_table_get_cell_style_zebra() {
1218 let zebra_color = Color::rgb(0.9, 0.9, 0.9);
1219 let table = AdvancedTableBuilder::new()
1220 .add_column("A", 50.0)
1221 .add_row(vec!["Row0"])
1222 .add_row(vec!["Row1"])
1223 .zebra_striping(zebra_color)
1224 .build()
1225 .unwrap();
1226
1227 let style_row1 = table.get_cell_style(1, 0);
1229 assert!(style_row1.background_color.is_some());
1230 }
1231
1232 #[test]
1233 fn test_table_get_cell_style_column_with_zebra() {
1234 let col_style = CellStyle::new().font_size(20.0);
1235 let zebra_color = Color::rgb(0.9, 0.9, 0.9);
1236 let table = AdvancedTableBuilder::new()
1237 .add_styled_column("A", 50.0, col_style)
1238 .add_row(vec!["Row0"])
1239 .add_row(vec!["Row1"])
1240 .zebra_striping(zebra_color)
1241 .build()
1242 .unwrap();
1243
1244 let style = table.get_cell_style(1, 0);
1246 assert_eq!(style.font_size, Some(20.0));
1247 assert!(style.background_color.is_some());
1248 }
1249
1250 #[test]
1251 fn test_table_validate_success() {
1252 let table = AdvancedTableBuilder::new()
1253 .add_column("A", 50.0)
1254 .add_column("B", 50.0)
1255 .add_row(vec!["1", "2"])
1256 .add_row(vec!["3", "4"])
1257 .build()
1258 .unwrap();
1259 assert!(table.validate().is_ok());
1260 }
1261
1262 #[test]
1263 fn test_table_validate_column_mismatch() {
1264 let mut table = AdvancedTableBuilder::new()
1265 .add_column("A", 50.0)
1266 .add_column("B", 50.0)
1267 .build()
1268 .unwrap();
1269
1270 table.rows.push(RowData::from_strings(vec!["1", "2", "3"]));
1272
1273 let result = table.validate();
1274 assert!(result.is_err());
1275 match result {
1276 Err(TableError::ColumnMismatch {
1277 row,
1278 found,
1279 expected,
1280 }) => {
1281 assert_eq!(row, 0);
1282 assert_eq!(found, 3);
1283 assert_eq!(expected, 2);
1284 }
1285 _ => panic!("Expected ColumnMismatch error"),
1286 }
1287 }
1288
1289 #[test]
1290 fn test_table_get_cell_style_default() {
1291 let default_style = CellStyle::new().font_size(12.0);
1292 let table = AdvancedTableBuilder::new()
1293 .add_column("A", 50.0)
1294 .add_row(vec!["Value"])
1295 .default_style(default_style.clone())
1296 .build()
1297 .unwrap();
1298
1299 let style = table.get_cell_style(0, 0);
1300 assert_eq!(style.font_size, Some(12.0));
1301 }
1302
1303 #[test]
1304 fn test_table_get_cell_style_invalid_row() {
1305 let table = AdvancedTableBuilder::new()
1306 .add_column("A", 50.0)
1307 .add_row(vec!["Value"])
1308 .build()
1309 .unwrap();
1310
1311 let style = table.get_cell_style(100, 0);
1313 assert_eq!(style.font_size, table.default_style.font_size);
1314 }
1315
1316 #[test]
1317 fn test_table_get_cell_style_invalid_column() {
1318 let table = AdvancedTableBuilder::new()
1319 .add_column("A", 50.0)
1320 .add_row(vec!["Value"])
1321 .build()
1322 .unwrap();
1323
1324 let style = table.get_cell_style(0, 100);
1326 assert_eq!(style.font_size, table.default_style.font_size);
1327 }
1328
1329 #[test]
1330 fn test_get_cell_style_ref_returns_correct_style() {
1331 use crate::graphics::Color;
1332
1333 let cell_style = CellStyle::new().font_size(18.0).text_color(Color::red());
1335 let table = AdvancedTableBuilder::new()
1336 .add_column("A", 100.0)
1337 .add_row(vec!["Value"])
1338 .set_cell_style(0, 0, cell_style.clone())
1339 .build()
1340 .unwrap();
1341
1342 let style_ref = table.get_cell_style_ref(0, 0);
1343 assert!(
1344 (style_ref.font_size.unwrap_or(12.0) - 18.0).abs() < f64::EPSILON,
1345 "Cell-specific style should have font_size 18.0"
1346 );
1347
1348 let table_default = AdvancedTableBuilder::new()
1350 .add_column("A", 100.0)
1351 .add_row(vec!["Value"])
1352 .build()
1353 .unwrap();
1354
1355 let style_ref_default = table_default.get_cell_style_ref(0, 0);
1356 assert_eq!(
1357 style_ref_default.font_size, table_default.default_style.font_size,
1358 "Should fall back to default_style when no overrides are set"
1359 );
1360
1361 let style_owned = table.get_cell_style(0, 0);
1363 let style_ref2 = table.get_cell_style_ref(0, 0);
1364 assert_eq!(
1365 style_owned.font_size, style_ref2.font_size,
1366 "get_cell_style and get_cell_style_ref should agree when no zebra striping"
1367 );
1368 }
1369}