use rdom_core::NodeId;
use crate::TuiDom;
use crate::layout::Size;
use crate::render::{Terminal, TestBackend};
use crate::runtime::app::App;
use crate::runtime::builtins::table;
use crate::style::{Stylesheet, Value};
fn two_row_table(row1: [&str; 2], row2: [&str; 2]) -> (TuiDom, [NodeId; 2], [NodeId; 2]) {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let table = dom.create_element("table");
let tbody = dom.create_element("tbody");
let build_row = |dom: &mut TuiDom, cells: [&str; 2]| -> ([NodeId; 2], NodeId) {
let tr = dom.create_element("tr");
let td0 = dom.create_element("td");
let t0 = dom.create_text_node(cells[0]);
dom.append_child(td0, t0).unwrap();
let td1 = dom.create_element("td");
let t1 = dom.create_text_node(cells[1]);
dom.append_child(td1, t1).unwrap();
dom.append_child(tr, td0).unwrap();
dom.append_child(tr, td1).unwrap();
([td0, td1], tr)
};
let (r1_cells, r1) = build_row(&mut dom, row1);
let (r2_cells, r2) = build_row(&mut dom, row2);
dom.append_child(tbody, r1).unwrap();
dom.append_child(tbody, r2).unwrap();
dom.append_child(table, tbody).unwrap();
dom.append_child(root, table).unwrap();
(dom, r1_cells, r2_cells)
}
fn cell_width(dom: &TuiDom, cell: NodeId) -> Option<u16> {
dom.node(cell)
.ext()
.and_then(|e| match e.inline_style.width {
Some(Value::Specified(Size::Fixed(w))) => Some(w),
_ => None,
})
}
fn test_app(dom: TuiDom) -> App<TestBackend> {
let backend = TestBackend::new(40, 5);
let terminal = Terminal::new(backend).unwrap();
App::with_backend(dom, Stylesheet::new(), terminal).unwrap()
}
#[test]
fn all_cells_in_the_same_column_get_equal_widths_after_sync() {
let (mut dom, r1, r2) = two_row_table(["Alice", "30"], ["Bob", "25"]);
let tables = {
let mut out = Vec::new();
for child in dom.node(dom.root()).child_nodes() {
if child.tag_name() == Some("table") {
out.push(child.id());
}
}
out
};
for t in tables {
table::size_columns(&mut dom, t);
}
assert_eq!(cell_width(&dom, r1[0]), Some(7));
assert_eq!(cell_width(&dom, r2[0]), Some(7));
assert_eq!(cell_width(&dom, r1[1]), Some(4));
assert_eq!(cell_width(&dom, r2[1]), Some(4));
}
#[test]
fn app_build_auto_syncs_all_tables_in_the_tree() {
let (dom, r1, r2) = two_row_table(["Alice", "Engineer"], ["Bob", "PM"]);
let app = test_app(dom);
assert_eq!(cell_width(app.dom(), r1[0]), Some(7));
assert_eq!(cell_width(app.dom(), r2[0]), Some(7));
assert_eq!(cell_width(app.dom(), r1[1]), Some(10));
assert_eq!(cell_width(app.dom(), r2[1]), Some(10));
}
#[test]
fn empty_table_is_noop() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let table = dom.create_element("table");
dom.append_child(root, table).unwrap();
table::size_columns(&mut dom, table);
}
#[test]
fn bare_tr_without_tbody_is_included() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let table = dom.create_element("table");
let tr1 = dom.create_element("tr");
let td1 = dom.create_element("td");
let t1 = dom.create_text_node("Wide content");
dom.append_child(td1, t1).unwrap();
dom.append_child(tr1, td1).unwrap();
let tr2 = dom.create_element("tr");
let td2 = dom.create_element("td");
let t2 = dom.create_text_node("x");
dom.append_child(td2, t2).unwrap();
dom.append_child(tr2, td2).unwrap();
dom.append_child(table, tr1).unwrap();
dom.append_child(table, tr2).unwrap();
dom.append_child(root, table).unwrap();
table::size_columns(&mut dom, table);
assert_eq!(cell_width(&dom, td1), Some(14));
assert_eq!(cell_width(&dom, td2), Some(14));
}
#[test]
fn thead_and_tbody_rows_both_contribute() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let table = dom.create_element("table");
let thead = dom.create_element("thead");
let htr = dom.create_element("tr");
let th = dom.create_element("th");
let th_text = dom.create_text_node("Header");
dom.append_child(th, th_text).unwrap();
dom.append_child(htr, th).unwrap();
dom.append_child(thead, htr).unwrap();
let tbody = dom.create_element("tbody");
let btr = dom.create_element("tr");
let td = dom.create_element("td");
let td_text = dom.create_text_node("X");
dom.append_child(td, td_text).unwrap();
dom.append_child(btr, td).unwrap();
dom.append_child(tbody, btr).unwrap();
dom.append_child(table, thead).unwrap();
dom.append_child(table, tbody).unwrap();
dom.append_child(root, table).unwrap();
table::size_columns(&mut dom, table);
assert_eq!(cell_width(&dom, th), Some(8));
assert_eq!(cell_width(&dom, td), Some(8));
}
#[test]
fn nested_element_text_is_measured() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let table = dom.create_element("table");
let tr = dom.create_element("tr");
let td = dom.create_element("td");
let b = dom.create_element("b");
let t = dom.create_text_node("bold");
dom.append_child(b, t).unwrap();
dom.append_child(td, b).unwrap();
dom.append_child(tr, td).unwrap();
dom.append_child(table, tr).unwrap();
dom.append_child(root, table).unwrap();
table::size_columns(&mut dom, table);
assert_eq!(cell_width(&dom, td), Some(6));
}
#[test]
fn size_all_tables_handles_multiple_tables() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let tbl1 = {
let t = dom.create_element("table");
let tr = dom.create_element("tr");
let td = dom.create_element("td");
let text = dom.create_text_node("AAA");
dom.append_child(td, text).unwrap();
dom.append_child(tr, td).unwrap();
dom.append_child(t, tr).unwrap();
dom.append_child(root, t).unwrap();
td
};
let tbl2 = {
let t = dom.create_element("table");
let tr = dom.create_element("tr");
let td = dom.create_element("td");
let text = dom.create_text_node("BBBBBB");
dom.append_child(td, text).unwrap();
dom.append_child(tr, td).unwrap();
dom.append_child(t, tr).unwrap();
dom.append_child(root, t).unwrap();
td
};
table::size_all_tables(&mut dom);
assert_eq!(cell_width(&dom, tbl1), Some(5)); assert_eq!(cell_width(&dom, tbl2), Some(8)); }
#[test]
fn cjk_graphemes_are_counted_as_wide() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let table = dom.create_element("table");
let tr = dom.create_element("tr");
let td = dom.create_element("td");
let t = dom.create_text_node("漢字");
dom.append_child(td, t).unwrap();
dom.append_child(tr, td).unwrap();
dom.append_child(table, tr).unwrap();
dom.append_child(root, table).unwrap();
table::size_columns(&mut dom, table);
assert_eq!(cell_width(&dom, td), Some(6));
}
fn first_table(dom: &TuiDom) -> NodeId {
dom.node(dom.root())
.child_nodes()
.find(|c| c.tag_name() == Some("table"))
.map(|c| c.id())
.expect("a <table> under root")
}
#[test]
fn size_columns_stamps_a_stable_colsync_signature() {
let (mut dom, _r1, _r2) = two_row_table(["Alice", "30"], ["Bob", "25"]);
let table = first_table(&dom);
table::size_columns(&mut dom, table);
let sig = dom
.get_attribute(table, "data-rdom-colsync")
.map(str::to_string);
assert!(sig.is_some(), "sizing stamps the column-width signature");
table::size_columns(&mut dom, table);
assert_eq!(
dom.get_attribute(table, "data-rdom-colsync")
.map(str::to_string),
sig
);
}
#[test]
fn size_columns_dirties_the_table_only_when_widths_change() {
let (mut dom, _r1, _r2) = two_row_table(["Alice", "30"], ["Bob", "25"]);
let table = first_table(&dom);
let tracker = crate::style::DirtyTracker::install(&mut dom);
table::size_columns(&mut dom, table);
assert!(
tracker.take_roots().contains(&table),
"the width change dirties the table so headers re-cascade"
);
table::size_columns(&mut dom, table);
assert!(
!tracker.take_roots().contains(&table),
"an unchanged re-size does not re-dirty the table"
);
}