use gilt::box_chars::{HEAVY, MINIMAL, ROUNDED, SQUARE};
use gilt::measure::Measurement;
use gilt::prelude::*;
use gilt::table::{ColumnOptions, Table};
use gilt::utils::align_widget::VerticalAlign;
fn capture_table(table: &Table, width: usize) -> String {
let mut c = Console::builder()
.width(width)
.force_terminal(false)
.no_color(true)
.build();
c.begin_capture();
c.print(table);
c.end_capture()
}
#[test]
fn render_table_basic() {
let mut table = Table::new(&["Name", "Age"]);
table.add_row(&["Alice", "30"]);
table.add_row(&["Bob", "25"]);
let out = capture_table(&table, 40);
assert!(out.contains("Alice"), "expected 'Alice' in output");
assert!(out.contains("Bob"), "expected 'Bob' in output");
assert!(out.contains("30"), "expected '30' in output");
assert!(out.contains("25"), "expected '25' in output");
assert!(out.contains('┏'), "expected heavy-head top-left '┏'");
assert!(out.contains('┡'), "expected header separator '┡'");
assert!(out.contains('└'), "expected bottom-left '└'");
}
#[test]
fn no_columns_no_panic() {
let table = Table::new(&[]);
let out = capture_table(&table, 40);
assert!(
out.trim().is_empty() || out == "\n",
"expected empty/newline output for empty table, got: {out:?}"
);
}
#[test]
fn add_row_auto_creates_columns() {
let mut table = Table::new(&[]);
table.add_row(&["a", "b", "c"]);
assert_eq!(table.columns.len(), 3, "expected 3 auto-created columns");
assert_eq!(table.row_count(), 1);
let out = capture_table(&table, 40);
assert!(out.contains('a') || !out.is_empty());
}
#[test]
fn measure_returns_nonzero_for_populated_table() {
let mut table = Table::new(&["Name", "Score"]);
table.add_row(&["Averlongword", "9999"]);
let c = Console::builder()
.width(80)
.force_terminal(false)
.no_color(true)
.build();
let opts = c.options();
let m = table.measure(&c, &opts);
assert!(m.minimum > 0, "minimum should be > 0");
assert!(m.maximum >= m.minimum, "maximum >= minimum");
}
#[test]
fn measure_zero_width_returns_zero() {
let table = Table::new(&["header"]).with_width(0);
let c = Console::builder().width(80).force_terminal(false).build();
let opts = c.options().update_width(0);
let m = table.measure(&c, &opts);
assert_eq!(m, Measurement::new(0, 0));
}
#[test]
fn width_clamps_output() {
let mut table = Table::new(&["foo"]).with_width(20);
table.add_row(&["bar"]);
let out = capture_table(&table, 80);
for line in out.lines() {
assert!(
line.chars().count() <= 20,
"line wider than 20 chars: {line:?} (chars={})",
line.chars().count()
);
}
}
#[test]
fn row_styles_cycle() {
let mut table =
Table::new(&["Val"]).with_row_styles(vec!["bold".to_string(), "italic".to_string()]);
table.add_row(&["row0"]);
table.add_row(&["row1"]);
table.add_row(&["row2"]);
assert_eq!(table.row_styles.len(), 2);
let out = capture_table(&table, 40);
assert!(out.contains("row0"));
assert!(out.contains("row2"));
}
#[test]
fn vertical_align_top_does_not_panic() {
let mut table = Table::new(&[])
.with_show_header(false)
.with_box_chars(Some(&SQUARE));
table.add_column(
"",
"",
ColumnOptions {
vertical: Some(VerticalAlign::Top),
..Default::default()
},
);
table.add_column("", "", ColumnOptions::default());
table.add_row(&["foo", "bar\nbar\nbar"]);
let out = capture_table(&table, 40);
assert!(out.contains("foo"));
assert!(out.contains("bar"));
}
#[test]
fn vertical_align_middle_does_not_panic() {
let mut table = Table::new(&[])
.with_show_header(false)
.with_box_chars(Some(&SQUARE));
table.add_column(
"",
"",
ColumnOptions {
vertical: Some(VerticalAlign::Middle),
..Default::default()
},
);
table.add_column("", "", ColumnOptions::default());
table.add_row(&["foo", "bar\nbar\nbar"]);
let _out = capture_table(&table, 40);
}
#[test]
fn vertical_align_bottom_does_not_panic() {
let mut table = Table::new(&[])
.with_show_header(false)
.with_box_chars(Some(&SQUARE));
table.add_column(
"",
"",
ColumnOptions {
vertical: Some(VerticalAlign::Bottom),
..Default::default()
},
);
table.add_column("", "", ColumnOptions::default());
table.add_row(&["foo", "bar\nbar\nbar"]);
let _out = capture_table(&table, 40);
}
#[test]
fn show_header_false_omits_header() {
let mut table = Table::new(&["HEADER_SENTINEL"]).with_show_header(false);
table.add_row(&["data_row"]);
let out = capture_table(&table, 40);
assert!(!out.contains("HEADER_SENTINEL"), "header should be absent");
assert!(out.contains("data_row"), "data row should appear");
}
#[test]
fn section_inserts_separator() {
let mut table = Table::new(&["Item"]);
table.add_row(&["row1"]);
table.add_row(&["row2"]);
table.add_section(); table.add_row(&["row3"]);
let out = capture_table(&table, 40);
assert!(out.contains("row1"), "row1 should appear");
assert!(out.contains("row2"), "row2 should appear");
assert!(out.contains("row3"), "row3 should appear");
assert!(
out.contains('├') || out.contains('┼'),
"expected row-separator character (├ or ┼)"
);
}
#[test]
fn add_section_before_any_rows_is_noop() {
let mut table = Table::new(&["Item"]);
table.add_section(); table.add_row(&["only_row"]);
let out = capture_table(&table, 40);
assert!(out.contains("only_row"));
assert!(!out.contains('├'), "should not have mid-row separator");
}
#[test]
fn box_square_uses_square_corners() {
let mut table = Table::new(&["H"]).with_box_chars(Some(&SQUARE));
table.add_row(&["x"]);
let out = capture_table(&table, 40);
assert!(out.contains('┌'), "SQUARE box should have '┌' top-left");
assert!(out.contains('└'), "SQUARE box should have '└' bottom-left");
}
#[test]
fn box_heavy_uses_heavy_corners() {
let mut table = Table::new(&["H"]).with_box_chars(Some(&HEAVY));
table.add_row(&["x"]);
let out = capture_table(&table, 40);
assert!(out.contains('┏'), "HEAVY box should have '┏' top-left");
assert!(out.contains('┗'), "HEAVY box should have '┗' bottom-left");
}
#[test]
fn box_rounded_uses_rounded_corners() {
let mut table = Table::new(&["H"]).with_box_chars(Some(&ROUNDED));
table.add_row(&["x"]);
let out = capture_table(&table, 40);
assert!(out.contains('╭'), "ROUNDED box should have '╭' top-left");
assert!(out.contains('╰'), "ROUNDED box should have '╰' bottom-left");
}
#[test]
fn box_minimal_has_no_outer_edges() {
let mut table = Table::new(&["H"]).with_box_chars(Some(&MINIMAL));
table.add_row(&["x"]);
let out = capture_table(&table, 40);
assert!(!out.contains('┌'), "MINIMAL should not have '┌'");
assert!(!out.contains('┏'), "MINIMAL should not have '┏'");
}
#[test]
fn box_none_produces_no_border_chars() {
let mut table = Table::new(&["H"]).with_box_chars(None);
table.add_row(&["x"]);
let out = capture_table(&table, 40);
assert!(!out.contains('┌'), "no-box table should have no '┌'");
assert!(!out.contains('│'), "no-box table should have no '│'");
}
#[test]
fn auto_created_columns_inherit_highlight_flag() {
let mut table = Table::new(&[]).with_highlight(true);
table.add_row(&["val1", "val2"]);
assert!(
table.columns[0].highlight,
"column 0 should inherit highlight=true"
);
assert!(
table.columns[1].highlight,
"column 1 should inherit highlight=true"
);
}
#[test]
fn explicit_columns_inherit_highlight_from_table() {
let mut table = Table::new(&["H1", "H2"]);
table.highlight = true;
table.add_column("H3", "", Default::default());
assert!(
table.columns[2].highlight,
"added column should inherit table.highlight"
);
}
#[test]
fn padding_grid_with_explicit_padding_renders_spaced() {
let mut table = Table::grid(&[]).with_padding((0, 1, 0, 0));
for _ in 0..3 {
table.add_column(
"",
"",
ColumnOptions {
width: Some(3),
..Default::default()
},
);
}
table.add_row(&["aaa", "aaa", "aaa"]);
let out = capture_table(&table, 40);
assert!(out.contains("aaa"), "expected cell content 'aaa'");
}
#[test]
fn padding_default_consistent_with_get_padding_width() {
let mut table = Table::new(&["a", "b", "c"]);
table.padding = (0, 2, 0, 1);
assert_eq!(table.get_padding_width(0), 1 + 2); assert_eq!(table.get_padding_width(1), 1 + 2);
assert_eq!(table.get_padding_width(2), 1 + 2);
}
#[test]
fn caption_appears_below_table() {
let mut table = Table::new(&["Column"]).with_caption("Note");
table.add_row(&["some data row content"]);
let out = capture_table(&table, 40);
assert!(out.contains("Note"), "caption should appear in output");
let plain: String = out.chars().collect();
let bottom_pos = plain
.char_indices()
.filter_map(|(i, c)| {
if c == '└' || c == '┗' || c == '┘' {
Some(i)
} else {
None
}
})
.max();
let caption_pos = plain.find("Note");
if let (Some(b), Some(c)) = (bottom_pos, caption_pos) {
assert!(c > b, "caption should appear after table bottom edge");
}
}