use crossterm::style::Color;
use htui::html::{css_parse, html_parse};
use htui::layout::layout::TermMap;
use htui::tui::graph_api::TermLayout;
fn init() {
let _ = env_logger::builder().is_test(true).try_init();
}
fn setup(html: &str, css: &str) -> TermLayout {
let mut html_parser = html_parse::Parser::new(html);
let (root_id, mut arena, embedded) = html_parser.parse();
let combined = if css.is_empty() {
embedded
} else if embedded.is_empty() {
css.to_string()
} else {
format!("{embedded}\n{css}")
};
if !combined.trim().is_empty() {
let mut css_parser = css_parse::Parser::new(&combined);
let rules = css_parser.parse();
css_parse::css_into_node(&mut arena, root_id, &rules);
}
let term_map = TermMap {
columns: 80,
rows: 24,
};
let mut tl = TermLayout::new(&term_map, 1.0);
tl.draw(&arena, root_id, &term_map);
tl
}
fn row(tl: &TermLayout, y: usize) -> String {
let cols = 80usize;
let start = y.saturating_mul(cols);
tl.layout[start..start + cols]
.iter()
.map(|b| b.word.as_str())
.collect()
}
fn cell_word(tl: &TermLayout, x: usize, y: usize) -> String {
tl.layout[y * 80 + x].word.to_string()
}
fn cell_color(tl: &TermLayout, x: usize, y: usize) -> Option<Color> {
tl.layout[y * 80 + x].fcolor
}
#[test]
fn test_multicol_count_fragments_block_children() {
init();
let html = r#"
<div class="cols">
<div>A1</div><div>A2</div><div>B1</div><div>B2</div>
</div>
"#;
let css = ".cols { width: 60ch; column-count: 2; column-gap: 0ch; }";
let tl = setup(html, css);
assert_eq!(
row(&tl, 0),
format!("A1{}B1{}", " ".repeat(28), " ".repeat(48))
);
assert_eq!(
row(&tl, 1),
format!("A2{}B2{}", " ".repeat(28), " ".repeat(48))
);
assert_eq!(row(&tl, 2), " ".repeat(80), "content fits in two rows");
}
#[test]
fn test_multicol_balance_uneven_content() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div><div>C</div><div>D</div><div>E</div>
</div>
"#;
let css = ".cols { width: 60ch; column-count: 3; column-gap: 0ch; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 0, 1), "B");
assert_eq!(cell_word(&tl, 20, 0), "C");
assert_eq!(cell_word(&tl, 20, 1), "D");
assert_eq!(cell_word(&tl, 40, 0), "E");
assert_eq!(cell_word(&tl, 40, 1), " ");
assert_eq!(cell_word(&tl, 40, 2), " ");
}
#[test]
fn test_multicol_balance_packs_uneven_heights_browser_style() {
init();
let html = r#"
<div class="cols">
<div class="t">A</div><div class="t">B</div><div class="t">C</div>
</div>
"#;
let css = ".cols { width: 60ch; column-count: 2; column-gap: 0ch; } .t { padding: 1ch; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 1, 1), "A", "first child in column 0");
assert_eq!(
cell_word(&tl, 1, 4),
"B",
"second child packs under A in column 0"
);
assert_eq!(cell_word(&tl, 31, 1), "C", "third child starts column 1");
assert_eq!(cell_word(&tl, 31, 4), " ", "column 1 has no fourth row");
assert_eq!(cell_word(&tl, 1, 6), " ", "container is 6 rows tall");
}
#[test]
fn test_multicol_column_width_derives_count() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div><div>C</div><div>D</div>
</div>
"#;
let css = ".cols { column-width: 20ch; column-gap: 0ch; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 20, 0), "B");
assert_eq!(cell_word(&tl, 40, 0), "C");
assert_eq!(cell_word(&tl, 60, 0), "D");
assert_eq!(
cell_word(&tl, 20, 1),
" ",
"each column holds exactly one item"
);
}
#[test]
fn test_multicol_columns_shorthand() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div><div>C</div><div>D</div>
</div>
"#;
let css = ".cols { width: 60ch; columns: 30ch 2; column-gap: 0ch; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 0, 1), "B");
assert_eq!(cell_word(&tl, 30, 0), "C");
assert_eq!(cell_word(&tl, 30, 1), "D");
}
#[test]
fn test_multicol_gap_spaces_columns() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div>
</div>
"#;
let css = ".cols { column-count: 2; column-gap: 4ch; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 42, 0), "B");
assert_eq!(cell_word(&tl, 38, 0), " ", "gap is blank");
assert_eq!(cell_word(&tl, 41, 0), " ");
}
#[test]
fn test_multicol_percentage_gap_resolves_against_container() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div>
</div>
"#;
let css = ".cols { width: 60ch; column-count: 2; column-gap: 50%; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 45, 0), "B");
assert_eq!(cell_word(&tl, 15, 0), " ", "half-width gap");
}
#[test]
fn test_multicol_fill_auto_stacks_first_column() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div><div>C</div><div>D</div>
</div>
"#;
let css = ".cols { column-count: 2; column-gap: 0ch; column-fill: auto; height: 4ch; }";
let tl = setup(html, css);
for y in 0..4 {
assert_eq!(
cell_word(&tl, 0, y),
"ABCD".chars().nth(y).unwrap().to_string()
);
assert_eq!(
cell_word(&tl, 40, y),
" ",
"column 1 stays empty when column 0 fills the whole height"
);
}
}
#[test]
fn test_multicol_inline_text_fragments() {
init();
let html = r#"
<div class="cols">
AAAA<br>BBBB<br>CCCC<br>DDDD<br>EEEE<br>FFFF
</div>
"#;
let css = ".cols { width: 60ch; column-count: 3; column-gap: 0ch; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 1, 0), "A");
assert_eq!(cell_word(&tl, 0, 1), "B");
assert_eq!(cell_word(&tl, 20, 0), "C");
assert_eq!(cell_word(&tl, 20, 1), "D");
assert_eq!(cell_word(&tl, 40, 0), "E");
assert_eq!(cell_word(&tl, 40, 1), "F");
assert_eq!(cell_word(&tl, 40, 2), " ", "6 lines fill 2 rows per column");
}
#[test]
fn test_multicol_column_rule_painted() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div><div>C</div><div>D</div>
</div>
"#;
let css = ".cols { column-count: 2; column-gap: 4ch; column-rule: 1ch solid red; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 40, 0), "│", "rule glyph in the gap");
assert_eq!(
cell_color(&tl, 40, 0),
Some(Color::Rgb { r: 255, g: 0, b: 0 }),
"rule uses its colour"
);
assert_eq!(cell_word(&tl, 40, 1), "│", "rule spans the content height");
assert_eq!(cell_word(&tl, 39, 0), " ", "gap blank left of the rule");
assert_eq!(cell_word(&tl, 41, 0), " ");
}
#[test]
fn test_multicol_no_rule_without_style() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div>
</div>
"#;
let css = ".cols { column-count: 2; column-gap: 4ch; column-rule-color: blue; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 40, 0), " ", "no rule without a style");
}
#[test]
fn test_multicol_count_one_falls_back_to_single_column() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div>
</div>
"#;
let css = ".cols { column-count: 1; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 0, 1), "B");
assert_eq!(cell_word(&tl, 40, 0), " ");
}
#[test]
fn test_multicol_narrow_container_reduces_count() {
init();
let html = r#"
<div class="cols">
<div>A</div><div>B</div>
</div>
"#;
let css = ".cols { width: 4ch; column-count: 3; column-gap: 0ch; }";
let tl = setup(html, css);
assert_eq!(cell_word(&tl, 0, 0), "A");
assert_eq!(cell_word(&tl, 1, 0), "B", "4 cells fit 4 one-cell columns");
}