use rdom_core::NodeId;
use crate::TuiDom;
use crate::node::TuiNodeExt;
use crate::render::{Color, Terminal, TestBackend};
use crate::runtime::app::App;
use crate::style::{Stylesheet, TuiStyle};
fn style(dom: &mut TuiDom, css: &str) -> NodeId {
let s = dom.create_element("style");
let t = dom.create_text_node(css);
dom.append_child(s, t).unwrap();
s
}
fn app_with(styles: &[&str], sheet: Stylesheet) -> (App<TestBackend>, NodeId, Vec<NodeId>) {
let mut dom: TuiDom = TuiDom::new();
let root = dom.root();
let mut ids = Vec::new();
for css in styles {
let s = style(&mut dom, css);
dom.append_child(root, s).unwrap();
ids.push(s);
}
let p = dom.create_element("p");
let t = dom.create_text_node("x");
dom.append_child(p, t).unwrap();
dom.append_child(root, p).unwrap();
let terminal = Terminal::new(TestBackend::new(10, 3)).unwrap();
let mut app = App::with_backend(dom, sheet, terminal).unwrap();
app.advance(0).unwrap();
(app, p, ids)
}
fn app(styles: &[&str]) -> (App<TestBackend>, NodeId, Vec<NodeId>) {
app_with(styles, Stylesheet::bare())
}
fn fg(app: &App<TestBackend>, id: NodeId) -> Color {
app.dom().node(id).computed().expect("cascaded").fg
}
fn text_of(app: &App<TestBackend>, style: NodeId) -> NodeId {
app.dom().node(style).first_child().unwrap().id()
}
const RED: Color = Color::Rgb(255, 0, 0);
const BLUE: Color = Color::Rgb(0, 0, 255);
const GREEN: Color = Color::Rgb(0, 128, 0);
#[test]
fn a_style_element_present_at_mount_applies() {
let (app, p, _) = app(&["p { color: red; }"]);
assert_eq!(fg(&app, p), RED);
}
#[test]
fn editing_a_style_elements_text_restyles_on_the_next_frame() {
let (mut app, p, styles) = app(&["p { color: red; }"]);
let text = text_of(&app, styles[0]);
app.dom_mut()
.node_mut(text)
.set_node_value("p { color: blue; }")
.unwrap();
app.advance(0).unwrap();
assert_eq!(fg(&app, p), BLUE);
app.dom_mut()
.set_text_content(styles[0], "p { color: green; }")
.unwrap();
app.advance(0).unwrap();
assert_eq!(fg(&app, p), GREEN);
}
#[test]
fn inserting_a_style_element_adds_its_rules() {
let (mut app, p, _) = app(&[]);
assert_eq!(fg(&app, p), Color::Reset);
let s = style(app.dom_mut(), "p { color: red; }");
let wrapper = app.dom_mut().create_element("div");
app.dom_mut().append_child(wrapper, s).unwrap();
let root = app.dom().root();
app.dom_mut().append_child(root, wrapper).unwrap();
app.advance(0).unwrap();
assert_eq!(fg(&app, p), RED);
}
#[test]
fn removing_a_style_element_drops_its_rules() {
let (mut app, p, styles) = app(&["p { color: red; }"]);
let root = app.dom().root();
app.dom_mut().remove_child(root, styles[0]).unwrap();
app.advance(0).unwrap();
assert_eq!(fg(&app, p), Color::Reset);
}
#[test]
fn style_elements_cascade_in_document_order() {
let (mut app, p, styles) = app(&["p { color: red; }", "p { color: blue; }"]);
assert_eq!(fg(&app, p), BLUE, "the later sheet wins the tie");
let root = app.dom().root();
app.dom_mut()
.insert_before(root, styles[0], Some(p))
.unwrap();
app.advance(0).unwrap();
assert_eq!(fg(&app, p), RED);
}
#[test]
fn app_sheets_cascade_after_style_elements() {
let sheet = Stylesheet::bare().rule_unchecked("p", TuiStyle::new().fg(GREEN));
let (app, p, _) = app_with(&["p { color: red; }"], sheet);
assert_eq!(
fg(&app, p),
GREEN,
"an App sheet, like an adopted sheet, follows the document's sheets"
);
}
#[test]
fn the_inline_style_cssom_path_still_works() {
let (mut app, p, _) = app(&["p { color: red; }"]);
app.dom_mut()
.set_attribute(p, "style", "color: blue")
.unwrap();
app.advance(0).unwrap();
assert_eq!(fg(&app, p), BLUE);
}
#[test]
fn parse_warnings_are_reported_and_refreshed() {
let (mut app, _, styles) = app(&["p { colr: red; }"]);
assert_eq!(app.style_element_warnings().len(), 1);
let text = text_of(&app, styles[0]);
app.dom_mut()
.node_mut(text)
.set_node_value("p { color: red; }")
.unwrap();
app.advance(0).unwrap();
assert!(app.style_element_warnings().is_empty());
}
#[test]
fn a_deep_bottom_up_build_does_not_recurse_and_only_its_insertion_counts() {
let mut dom: TuiDom = TuiDom::new();
let elements = super::StyleElements::install(&mut dom);
elements.dirty.set(false);
let mut top = style(&mut dom, "p { color: red; }");
for _ in 0..10_000 {
let parent = dom.create_element("div");
dom.append_child(parent, top).unwrap();
top = parent;
}
assert!(!elements.dirty.get(), "a detached build holds no sheet");
let root = dom.root();
dom.append_child(root, top).unwrap();
assert!(elements.dirty.get(), "inserting it adds one");
elements.dirty.set(false);
dom.remove_child(root, top).unwrap();
assert!(elements.dirty.get(), "removing it drops it");
}
#[test]
fn mutations_without_a_connected_style_element_are_not_relevant() {
let mut dom: TuiDom = TuiDom::new();
let elements = super::StyleElements::install(&mut dom);
elements.dirty.set(false);
let root = dom.root();
let list = dom.create_element("ul");
dom.append_child(root, list).unwrap();
for _ in 0..3 {
let li = dom.create_element("li");
let t = dom.create_text_node("x");
dom.append_child(li, t).unwrap();
dom.append_child(list, li).unwrap();
}
let detached = style(&mut dom, "p {}");
let text = dom.node(detached).first_child().unwrap().id();
dom.node_mut(text)
.set_node_value("p { color: red; }")
.unwrap();
assert!(!elements.dirty.get());
}
#[test]
fn holds_style_searches_a_deep_chain_without_recursing() {
let mut dom: TuiDom = TuiDom::new();
let mut with = style(&mut dom, "");
let mut without = dom.create_element("span");
for _ in 0..10_000 {
for top in [&mut with, &mut without] {
let parent = dom.create_element("div");
dom.append_child(parent, *top).unwrap();
*top = parent;
}
}
assert!(super::holds_style(&dom, with));
assert!(!super::holds_style(&dom, without));
let s = style(&mut dom, "");
dom.append_child(without, s).unwrap();
assert!(super::holds_style(&dom, without));
}