use rdom_tui::prelude::*;
fn render(dom: &mut TuiDom, sheet: &Stylesheet, w: u16, h: u16) -> VirtualScreen {
let backend = TestBackend::new(w, h);
let mut term = Terminal::new(backend).unwrap();
term.draw(|buf| {
dom.cascade(sheet);
dom.layout_dom(buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})
.unwrap();
let mut screen = VirtualScreen::new(w, h);
screen.apply(term.backend().bytes());
screen
}
#[test]
fn single_line_inline_flow_renders_in_document_order() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("prefix ");
let b = dom.create_element("b");
let b_text = dom.create_text_node("bold");
dom.append_child(b, b_text).unwrap();
let t2 = dom.create_text_node(" suffix");
dom.append_child(p, t1).unwrap();
dom.append_child(p, b).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked("b", TuiStyle::new().display(Display::Inline).bold(true));
let screen = render(&mut dom, &sheet, 30, 1);
assert_eq!(screen.row(0).trim_end(), "prefix bold suffix");
}
#[test]
fn inline_element_styles_its_fragment() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("a ");
let code = dom.create_element("code");
let code_text = dom.create_text_node("X");
dom.append_child(code, code_text).unwrap();
let t2 = dom.create_text_node(" b");
dom.append_child(p, t1).unwrap();
dom.append_child(p, code).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked(
"code",
TuiStyle::new()
.display(Display::Inline)
.fg(Color::Rgb(255, 255, 0)),
);
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "a X b");
assert_eq!(screen.cell(0, 0).unwrap().fg, Color::Reset); assert_eq!(screen.cell(2, 0).unwrap().fg, Color::Rgb(255, 255, 0)); assert_eq!(screen.cell(3, 0).unwrap().fg, Color::Reset); assert_eq!(screen.cell(4, 0).unwrap().fg, Color::Reset); }
#[test]
fn nested_inline_elements_compose_styles() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("a ");
let b = dom.create_element("b");
let i = dom.create_element("i");
let bi_text = dom.create_text_node("bi");
dom.append_child(i, bi_text).unwrap();
dom.append_child(b, i).unwrap();
let t2 = dom.create_text_node(" c");
dom.append_child(p, t1).unwrap();
dom.append_child(p, b).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked("b", TuiStyle::new().display(Display::Inline).bold(true))
.rule_unchecked("i", TuiStyle::new().display(Display::Inline).italic(true));
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "a bi c");
let cell = screen.cell(2, 0).unwrap();
assert!(cell.modifier.contains(Modifier::BOLD), "nested inline bold");
assert!(
cell.modifier.contains(Modifier::ITALIC),
"nested inline italic"
);
}
#[test]
fn ifc_clips_overflow_at_content_width() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("0123456789ABCDEF");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
let span_text = dom.create_text_node("!");
dom.append_child(span, span_text).unwrap();
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.width(Size::Fixed(8))
.height(Size::Fixed(1)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 20, 1);
assert_eq!(&screen.row(0)[..8], "01234567");
assert_eq!(screen.row(0)[8..].trim_end(), "");
}
#[test]
fn block_with_only_block_children_not_ifc() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let section = dom.create_element("section");
let p1 = dom.create_element("p");
let t1 = dom.create_text_node("first");
dom.append_child(p1, t1).unwrap();
let p2 = dom.create_element("p");
let t2 = dom.create_text_node("second");
dom.append_child(p2, t2).unwrap();
dom.append_child(section, p1).unwrap();
dom.append_child(section, p2).unwrap();
dom.append_child(root, section).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"section",
TuiStyle::new()
.display(Display::Block)
.flow(Flow::Flex)
.direction(Direction::Column),
)
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
);
let screen = render(&mut dom, &sheet, 10, 3);
assert_eq!(screen.row(0).trim_end(), "first");
assert_eq!(screen.row(1).trim_end(), "second");
}
#[test]
fn ifc_with_only_text_no_inline_elements_not_ifc() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("hello");
dom.append_child(p, t).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare().rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
);
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "hello");
}
#[test]
fn whitespace_runs_collapse_to_single_space() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("foo bar\n\nbaz");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
let st = dom.create_text_node("");
dom.append_child(span, st).unwrap();
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 20, 1);
assert_eq!(screen.row(0).trim_end(), "foo bar baz");
}
#[test]
fn leading_whitespace_trimmed_at_ifc_start() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("\n ");
let b = dom.create_element("b");
let bt = dom.create_text_node("bold");
dom.append_child(b, bt).unwrap();
let t2 = dom.create_text_node(" after");
dom.append_child(p, t1).unwrap();
dom.append_child(p, b).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked("b", TuiStyle::new().display(Display::Inline).bold(true));
let screen = render(&mut dom, &sheet, 20, 1);
assert_eq!(screen.row(0).trim_end(), "bold after");
}
#[test]
fn trailing_whitespace_trimmed_at_ifc_end() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("hello ");
let span = dom.create_element("span");
let st = dom.create_text_node("world");
dom.append_child(span, st).unwrap();
let t2 = dom.create_text_node(" \n "); dom.append_child(p, t1).unwrap();
dom.append_child(p, span).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 20, 1);
assert_eq!(screen.row(0).trim_end(), "hello world");
assert_eq!(screen.cell(11, 0).unwrap().symbol(), " ");
}
#[test]
fn whitespace_across_node_boundaries_collapses_to_one_space() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("a ");
let span = dom.create_element("span");
let sp_t = dom.create_text_node(" ");
dom.append_child(span, sp_t).unwrap();
let t2 = dom.create_text_node(" b");
dom.append_child(p, t1).unwrap();
dom.append_child(p, span).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "a b");
}
#[test]
fn nbsp_is_not_collapsed() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("a\u{00A0}\u{00A0}\u{00A0}b");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
let st = dom.create_text_node("");
dom.append_child(span, st).unwrap();
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "a\u{00A0}\u{00A0}\u{00A0}b");
}
#[test]
fn white_space_pre_preserves_spaces() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("a b");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
let st = dom.create_text_node("");
dom.append_child(span, st).unwrap();
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.white_space(WhiteSpace::Pre)
.height(Size::Fixed(1)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "a b");
}
#[test]
fn long_paragraph_wraps_at_word_boundaries() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("the quick brown fox jumps");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.width(Size::Fixed(10)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 10, 4);
assert_eq!(screen.row(0).trim_end(), "the quick");
assert_eq!(screen.row(1).trim_end(), "brown fox");
assert_eq!(screen.row(2).trim_end(), "jumps");
assert_eq!(screen.row(3).trim_end(), "");
}
#[test]
fn wrapped_inline_styling_preserved_across_lines() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("aaa ");
let b = dom.create_element("b");
let bt = dom.create_text_node("bbb ccc");
dom.append_child(b, bt).unwrap();
let t2 = dom.create_text_node(" ddd");
dom.append_child(p, t1).unwrap();
dom.append_child(p, b).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.width(Size::Fixed(8)),
)
.rule_unchecked(
"b",
TuiStyle::new()
.display(Display::Inline)
.bold(true)
.fg(Color::Rgb(255, 255, 0)),
);
let screen = render(&mut dom, &sheet, 10, 2);
assert_eq!(screen.row(0).trim_end(), "aaa bbb");
assert_eq!(screen.row(1).trim_end(), "ccc ddd");
let bbb_cell = screen.cell(4, 0).unwrap();
assert_eq!(bbb_cell.symbol(), "b");
assert!(bbb_cell.modifier.contains(Modifier::BOLD));
assert_eq!(bbb_cell.fg, Color::Rgb(255, 255, 0));
let ccc_cell = screen.cell(0, 1).unwrap();
assert_eq!(ccc_cell.symbol(), "c");
assert!(ccc_cell.modifier.contains(Modifier::BOLD));
assert_eq!(ccc_cell.fg, Color::Rgb(255, 255, 0));
let ddd_cell = screen.cell(4, 1).unwrap();
assert_eq!(ddd_cell.symbol(), "d");
assert!(!ddd_cell.modifier.contains(Modifier::BOLD));
}
#[test]
fn cjk_wraps_between_graphemes() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("中文中文中文");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.width(Size::Fixed(6)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 10, 3);
assert_eq!(screen.row(0).trim_end(), "中文中");
assert_eq!(screen.row(1).trim_end(), "文中文");
}
#[test]
fn auto_height_ifc_grows_to_fit_content() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let section = dom.create_element("section");
let p = dom.create_element("p");
let t = dom.create_text_node("one two three four five");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
dom.append_child(p, span).unwrap();
dom.append_child(section, p).unwrap();
let footer = dom.create_element("footer");
let ft = dom.create_text_node("END");
dom.append_child(footer, ft).unwrap();
dom.append_child(section, footer).unwrap();
dom.append_child(root, section).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"section",
TuiStyle::new()
.display(Display::Block)
.flow(Flow::Flex)
.direction(Direction::Column)
.width(Size::Fixed(10)),
)
.rule_unchecked("p", TuiStyle::new().display(Display::Block))
.rule_unchecked("span", TuiStyle::new().display(Display::Inline))
.rule_unchecked(
"footer",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
);
let screen = render(&mut dom, &sheet, 10, 6);
assert_eq!(screen.row(0).trim_end(), "one two");
assert_eq!(screen.row(1).trim_end(), "three four");
assert_eq!(screen.row(2).trim_end(), "five");
assert_eq!(screen.row(3).trim_end(), "END");
}
#[test]
fn fixed_height_ifc_clips_overflowing_lines() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("one two three four five six seven");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.width(Size::Fixed(10))
.height(Size::Fixed(2)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 10, 4);
assert_eq!(screen.row(0).trim_end(), "one two");
assert_eq!(screen.row(1).trim_end(), "three four");
assert_eq!(screen.row(2).trim_end(), "");
assert_eq!(screen.row(3).trim_end(), "");
}
#[test]
fn hyphenated_word_breaks_after_hyphen() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("state-of-the-art");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"p",
TuiStyle::new()
.display(Display::Block)
.width(Size::Fixed(10)),
)
.rule_unchecked("span", TuiStyle::new().display(Display::Inline));
let screen = render(&mut dom, &sheet, 10, 2);
assert_eq!(screen.row(0).trim_end(), "state-of-");
assert_eq!(screen.row(1).trim_end(), "the-art");
}
#[test]
fn br_forces_hard_line_break() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("first");
let br = dom.create_element("br");
let t2 = dom.create_text_node("second");
dom.append_child(p, t1).unwrap();
dom.append_child(p, br).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::new()
.rule("p", TuiStyle::new().width(Size::Fixed(10)))
.unwrap();
let screen = render(&mut dom, &sheet, 10, 3);
assert_eq!(screen.row(0).trim_end(), "first");
assert_eq!(screen.row(1).trim_end(), "second");
}
#[test]
fn double_br_produces_blank_line() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("a");
let br1 = dom.create_element("br");
let br2 = dom.create_element("br");
let t2 = dom.create_text_node("b");
dom.append_child(p, t1).unwrap();
dom.append_child(p, br1).unwrap();
dom.append_child(p, br2).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::new()
.rule("p", TuiStyle::new().width(Size::Fixed(10)))
.unwrap();
let screen = render(&mut dom, &sheet, 10, 3);
assert_eq!(screen.row(0).trim_end(), "a");
assert_eq!(screen.row(1).trim_end(), "");
assert_eq!(screen.row(2).trim_end(), "b");
}
#[test]
fn pre_mode_newline_is_hard_break() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let pre = dom.create_element("pre");
let t = dom.create_text_node("line1\nline2\nline3");
dom.append_child(pre, t).unwrap();
let span = dom.create_element("span");
dom.append_child(pre, span).unwrap();
dom.append_child(root, pre).unwrap();
let sheet = Stylesheet::new();
let screen = render(&mut dom, &sheet, 20, 3);
assert_eq!(screen.row(0).trim_end(), "line1");
assert_eq!(screen.row(1).trim_end(), "line2");
assert_eq!(screen.row(2).trim_end(), "line3");
}
#[test]
fn pre_mode_preserves_multiple_spaces() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let pre = dom.create_element("pre");
let t = dom.create_text_node("a b");
dom.append_child(pre, t).unwrap();
let span = dom.create_element("span");
dom.append_child(pre, span).unwrap();
dom.append_child(root, pre).unwrap();
let sheet = Stylesheet::new();
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "a b");
}
#[test]
fn ua_defaults_bold_via_b_tag() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t1 = dom.create_text_node("a ");
let b = dom.create_element("b");
let bt = dom.create_text_node("bold");
dom.append_child(b, bt).unwrap();
let t2 = dom.create_text_node(" c");
dom.append_child(p, t1).unwrap();
dom.append_child(p, b).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::new()
.rule("p", TuiStyle::new().height(Size::Fixed(1)))
.unwrap();
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "a bold c");
assert!(screen.cell(2, 0).unwrap().modifier.contains(Modifier::BOLD));
assert!(screen.cell(5, 0).unwrap().modifier.contains(Modifier::BOLD));
assert!(!screen.cell(0, 0).unwrap().modifier.contains(Modifier::BOLD));
assert!(!screen.cell(7, 0).unwrap().modifier.contains(Modifier::BOLD));
}
#[test]
fn ua_defaults_italic_via_em_tag() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let em = dom.create_element("em");
let t = dom.create_text_node("italic");
dom.append_child(em, t).unwrap();
dom.append_child(p, em).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::new()
.rule("p", TuiStyle::new().height(Size::Fixed(1)))
.unwrap();
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "italic");
assert!(
screen
.cell(0, 0)
.unwrap()
.modifier
.contains(Modifier::ITALIC)
);
}
#[test]
fn ua_defaults_code_yellow_foreground() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let code = dom.create_element("code");
let t = dom.create_text_node("x");
dom.append_child(code, t).unwrap();
dom.append_child(p, code).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::new()
.rule("p", TuiStyle::new().height(Size::Fixed(1)))
.unwrap();
let screen = render(&mut dom, &sheet, 10, 1);
assert_eq!(screen.row(0).trim_end(), "x");
assert_eq!(screen.cell(0, 0).unwrap().fg, Color::Rgb(255, 215, 0));
}
#[test]
fn resizing_terminal_wider_then_narrower_reflows_ifc_without_stale_cells() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let screen = dom.create_element("screen");
let body = dom.create_element("body");
let p = dom.create_element("p");
let t1 = dom.create_text_node("hello ");
let b = dom.create_element("b");
let bt = dom.create_text_node("wrapping");
dom.append_child(b, bt).unwrap();
let t2 = dom.create_text_node(" world of inline text");
dom.append_child(p, t1).unwrap();
dom.append_child(p, b).unwrap();
dom.append_child(p, t2).unwrap();
dom.append_child(body, p).unwrap();
dom.append_child(screen, body).unwrap();
dom.append_child(root, screen).unwrap();
let sheet = Stylesheet::new()
.rule(
"screen",
TuiStyle::new()
.flow(Flow::Flex)
.direction(Direction::Column)
.width(Size::Flex(1))
.height(Size::Flex(1)),
)
.unwrap()
.rule(
"body",
TuiStyle::new()
.flow(Flow::Flex)
.direction(Direction::Row)
.padding(Padding::all(1))
.height(Size::Flex(1)),
)
.unwrap()
.rule(
"p",
TuiStyle::new()
.width(Size::Flex(1))
.border(Border::rounded())
.padding(Padding::all(1)),
)
.unwrap();
let backend = TestBackend::new(40, 10);
let mut term = Terminal::new(backend).unwrap();
term.draw(|buf| {
dom.cascade(&sheet);
dom.layout_dom(buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})
.unwrap();
let mut screen40 = VirtualScreen::new(40, 10);
screen40.apply(term.backend().bytes());
term.backend_mut().resize(20, 10);
let mut screen20 = VirtualScreen::new(20, 10);
let _ = term.backend_mut().take_bytes();
term.draw(|buf| {
dom.cascade(&sheet);
dom.layout_dom(buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})
.unwrap();
screen20.apply(term.backend().bytes());
for y in 0..10 {
assert!(screen20.cell(19, y).is_some());
assert!(screen20.cell(20, y).is_none());
}
term.backend_mut().resize(60, 10);
let mut screen60 = VirtualScreen::new(60, 10);
let _ = term.backend_mut().take_bytes();
term.draw(|buf| {
dom.cascade(&sheet);
dom.layout_dom(buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})
.unwrap();
screen60.apply(term.backend().bytes());
for y in 0..10 {
assert!(screen60.cell(59, y).is_some());
assert!(screen60.cell(60, y).is_none());
}
let row3 = screen60.row(3);
assert!(
row3.contains("hello") || row3.contains("wrapping") || row3.contains("world"),
"row 3 should contain inline text after wider resize: {:?}",
row3
);
}
#[test]
fn ifc_reflows_when_only_width_changes_same_height() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let p = dom.create_element("p");
let t = dom.create_text_node("aaa bbb ccc ddd eee fff ggg hhh");
dom.append_child(p, t).unwrap();
let span = dom.create_element("span");
dom.append_child(p, span).unwrap();
dom.append_child(root, p).unwrap();
let sheet = Stylesheet::new()
.rule(
"p",
TuiStyle::new().display(Display::Block).width(Size::Flex(1)),
)
.unwrap();
let backend = TestBackend::new(30, 8);
let mut term = Terminal::new(backend).unwrap();
term.draw(|buf| {
dom.cascade(&sheet);
dom.layout_dom(buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})
.unwrap();
let mut s30 = VirtualScreen::new(30, 8);
s30.apply(term.backend().bytes());
term.backend_mut().resize(10, 8);
let _ = term.backend_mut().take_bytes();
term.draw(|buf| {
dom.cascade(&sheet);
dom.layout_dom(buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})
.unwrap();
let mut s10 = VirtualScreen::new(10, 8);
s10.apply(term.backend().bytes());
let line0 = s10.row(0).trim_end().to_string();
assert!(
!line0.is_empty(),
"row 0 empty after narrow reflow — content lost"
);
for y in 0..8 {
assert!(s10.cell(9, y).is_some());
assert!(s10.cell(10, y).is_none());
}
term.backend_mut().resize(30, 8);
let _ = term.backend_mut().take_bytes();
term.draw(|buf| {
dom.cascade(&sheet);
dom.layout_dom(buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})
.unwrap();
let mut s30_again = VirtualScreen::new(30, 8);
s30_again.apply(term.backend().bytes());
for y in 0..8 {
assert_eq!(
s30.row(y).trim_end(),
s30_again.row(y).trim_end(),
"row {y} differs between first 30-wide render and post-resize 30-wide render"
);
}
}
#[test]
fn ifc_block_intrinsic_height_is_one_line() {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let section = dom.create_element("section");
let p = dom.create_element("p");
let t = dom.create_text_node("x ");
let b = dom.create_element("b");
let b_text = dom.create_text_node("y");
dom.append_child(b, b_text).unwrap();
dom.append_child(p, t).unwrap();
dom.append_child(p, b).unwrap();
dom.append_child(section, p).unwrap();
let after = dom.create_element("div");
let after_text = dom.create_text_node("after");
dom.append_child(after, after_text).unwrap();
dom.append_child(section, after).unwrap();
dom.append_child(root, section).unwrap();
let sheet = Stylesheet::bare()
.rule_unchecked(
"section",
TuiStyle::new()
.display(Display::Block)
.flow(Flow::Flex)
.direction(Direction::Column),
)
.rule_unchecked("p", TuiStyle::new().display(Display::Block))
.rule_unchecked("b", TuiStyle::new().display(Display::Inline).bold(true))
.rule_unchecked(
"div",
TuiStyle::new()
.display(Display::Block)
.height(Size::Fixed(1)),
);
let screen = render(&mut dom, &sheet, 10, 3);
assert_eq!(screen.row(0).trim_end(), "x y");
assert_eq!(screen.row(1).trim_end(), "after");
}