use azul_core::{
dom::{Dom, NodeType},
prop_cache::CssPropertyOrigin,
styled_dom::StyledDom,
};
use azul_css::{
css::Css,
dynamic_selector::CssPropertyWithConditions,
props::{
basic::font::{StyleFontSize, StyleFontWeight},
property::{CssProperty, CssPropertyType},
},
};
macro_rules! setup_test {
($dom:expr) => {{
let mut dom = $dom;
let styled_dom = StyledDom::create(&mut dom, Css::empty());
let cache = styled_dom.css_property_cache.ptr.clone();
(styled_dom, cache)
}};
}
#[test]
fn test_font_size_inheritance_single_level() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(24.0),
))]
.into(),
)
.with_child(Dom::create_node(NodeType::P).with_child(Dom::create_text("Text")));
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
let changed_nodes = cache.compute_inherited_values(node_hierarchy, node_data);
println!("Changed nodes: {:?}", changed_nodes);
println!("Computed values: {:#?}", cache.computed_values);
let parent_id = azul_core::dom::NodeId::new(0); let child_id = azul_core::dom::NodeId::new(1);
if let Some(child_computed) = cache.computed_values.get(child_id.index()) {
let Some(prop_with_origin) = child_computed.get(&CssPropertyType::FontSize) else {
panic!("Child should have FontSize");
};
if let CssProperty::FontSize(font_size_value) = &prop_with_origin.property {
if let Some(font_size) = font_size_value.get_property() {
let size = font_size.inner.to_pixels_internal(16.0, 16.0); assert_eq!(
size, 24.0,
"Child should inherit parent's font-size of 24px"
);
} else {
panic!("FontSize value should not be None/Auto/Initial/Inherit");
}
} else {
panic!("Child should have computed FontSize property");
}
} else {
panic!("Child should have computed values");
}
}
#[test]
fn test_font_size_override_not_inherited() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(24.0),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::P)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(12.0),
))]
.into(),
)
.with_child(Dom::create_text("Text")),
);
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
let changed_nodes = cache.compute_inherited_values(node_hierarchy, node_data);
println!("Changed nodes: {:?}", changed_nodes);
println!("Computed values: {:#?}", cache.computed_values);
let child_id = azul_core::dom::NodeId::new(1);
if let Some(child_computed) = cache.computed_values.get(child_id.index()) {
let Some(prop_with_origin) = child_computed.get(&CssPropertyType::FontSize) else {
panic!("Child should have FontSize");
};
if let CssProperty::FontSize(font_size_value) = &prop_with_origin.property {
if let Some(font_size) = font_size_value.get_property() {
let size = font_size.inner.to_pixels_internal(16.0, 16.0);
assert_eq!(
size, 12.0,
"Child should use its explicit font-size of 12px, not inherit 24px"
);
} else {
panic!("FontSize value should not be None/Auto/Initial/Inherit");
}
} else {
panic!("Child should have computed FontSize property");
}
} else {
panic!("Child should have computed values");
}
}
#[test]
fn test_font_weight_inheritance_multi_level() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_weight(
StyleFontWeight::Bold,
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::P)
.with_child(Dom::create_node(NodeType::Span).with_child(Dom::create_text("Text"))),
);
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
let changed_nodes = cache.compute_inherited_values(node_hierarchy, node_data);
println!("Changed nodes: {:?}", changed_nodes);
println!("Computed values: {:#?}", cache.computed_values);
let div_id = azul_core::dom::NodeId::new(0); let p_id = azul_core::dom::NodeId::new(1); let span_id = azul_core::dom::NodeId::new(2);
for (node_id, node_name) in &[(p_id, "p"), (span_id, "span")] {
if let Some(computed) = cache.computed_values.get(node_id.index()) {
let Some(prop_with_origin) = computed.get(&CssPropertyType::FontWeight) else {
panic!("{} should have FontWeight", node_name);
};
if let CssProperty::FontWeight(font_weight_value) = &prop_with_origin.property {
if let Some(font_weight) = font_weight_value.get_property() {
assert_eq!(
*font_weight,
StyleFontWeight::Bold,
"{} should inherit font-weight: bold from ancestor div",
node_name
);
} else {
panic!(
"{} FontWeight value should not be None/Auto/Initial/Inherit",
node_name
);
}
} else {
panic!("{} should have computed FontWeight property", node_name);
}
} else {
panic!("{} should have computed values", node_name);
}
}
}
#[test]
fn test_mixed_inherited_and_explicit_properties() {
let dom = Dom::create_div()
.with_css_props(
vec![
CssPropertyWithConditions::simple(CssProperty::font_size(StyleFontSize::px(20.0))),
CssPropertyWithConditions::simple(CssProperty::font_weight(StyleFontWeight::Bold)),
]
.into(),
)
.with_child(
Dom::create_node(NodeType::P)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(16.0),
))]
.into(),
)
.with_child(Dom::create_text("Text")),
);
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
cache.compute_inherited_values(node_hierarchy, node_data);
let p_id = azul_core::dom::NodeId::new(1);
if let Some(p_computed) = cache.computed_values.get(p_id.index()) {
let Some(prop_with_origin) = p_computed.get(&CssPropertyType::FontSize) else {
panic!("p should have computed FontSize");
};
if let CssProperty::FontSize(font_size_value) = &prop_with_origin.property {
if let Some(font_size) = font_size_value.get_property() {
let size = font_size.inner.to_pixels_internal(16.0, 16.0);
assert_eq!(size, 16.0, "p should have explicit font-size: 16px");
}
} else {
panic!("p FontSize should be CssProperty::FontSize variant");
}
let Some(prop_with_origin) = p_computed.get(&CssPropertyType::FontWeight) else {
panic!("p should have FontWeight");
};
if let CssProperty::FontWeight(font_weight_value) = &prop_with_origin.property {
if let Some(font_weight) = font_weight_value.get_property() {
assert_eq!(
*font_weight,
StyleFontWeight::Bold,
"p should inherit font-weight: bold from div"
);
}
} else {
panic!("p should have computed FontWeight inherited from parent");
}
} else {
panic!("p should have computed values");
}
}
#[test]
fn test_non_inheritable_property_not_inherited() {
use azul_css::{
css::CssPropertyValue,
props::{basic::pixel::PixelValue, layout::dimensions::LayoutWidth},
};
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::Width(
CssPropertyValue::Exact(LayoutWidth::Px(PixelValue::px(200.0))),
))]
.into(),
)
.with_child(Dom::create_node(NodeType::P).with_child(Dom::create_text("Text")));
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
cache.compute_inherited_values(node_hierarchy, node_data);
let p_id = azul_core::dom::NodeId::new(1);
if let Some(p_computed) = cache.computed_values.get(p_id.index()) {
if let Some(prop_with_origin) = p_computed.get(&CssPropertyType::Width) {
assert_eq!(
prop_with_origin.origin,
CssPropertyOrigin::Own,
"Width origin should be Own (from UA CSS), not Inherited from parent. This proves \
that non-inheritable properties are not inherited."
);
}
}
}
#[test]
fn test_update_invalidation() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(20.0),
))]
.into(),
)
.with_children(
vec![
Dom::create_node(NodeType::P).with_child(Dom::create_text("Child 1")),
Dom::create_node(NodeType::P).with_child(Dom::create_text("Child 2")),
]
.into(),
);
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
cache.computed_values.iter_mut().for_each(|m| m.clear());
cache.dependency_chains.iter_mut().for_each(|m| m.clear());
let changed_nodes_1 = cache.compute_inherited_values(node_hierarchy, node_data);
println!("First computation changed nodes: {:?}", changed_nodes_1);
assert!(
changed_nodes_1.len() >= 3,
"All nodes should change on first computation"
);
let changed_nodes_2 = cache.compute_inherited_values(node_hierarchy, node_data);
println!("Second computation changed nodes: {:?}", changed_nodes_2);
assert!(
changed_nodes_2.is_empty(),
"No nodes should change when recomputing with same values"
);
}
#[test]
fn test_deeply_nested_inheritance() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_weight(
StyleFontWeight::Bold,
))]
.into(),
)
.with_child(Dom::create_node(NodeType::Section).with_child(
Dom::create_node(NodeType::Article).with_child(
Dom::create_node(NodeType::P).with_child(
Dom::create_node(NodeType::Span).with_child(Dom::create_text("Deep text")),
),
),
));
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
cache.compute_inherited_values(node_hierarchy, node_data);
println!("Node hierarchy: {:#?}", node_hierarchy);
println!("Computed values: {:#?}", cache.computed_values);
let span_id = azul_core::dom::NodeId::new(4);
let Some(span_computed) = cache.computed_values.get(span_id.index()) else {
panic!("Deeply nested span should have computed values");
};
let Some(prop_with_origin) = span_computed.get(&CssPropertyType::FontWeight) else {
panic!("Deeply nested span should have inherited FontWeight");
};
let CssProperty::FontWeight(font_weight_value) = &prop_with_origin.property else {
panic!("FontWeight should be CssProperty::FontWeight variant");
};
let Some(font_weight) = font_weight_value.get_property() else {
panic!("FontWeight should have explicit value");
};
assert_eq!(
*font_weight,
StyleFontWeight::Bold,
"Deeply nested span should inherit font-weight: bold"
);
}
#[test]
fn test_em_unit_inheritance_basic() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(16.0),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::P)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::em(2.0),
))]
.into(),
)
.with_child(Dom::create_text("Text")),
);
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
cache.compute_inherited_values(node_hierarchy, node_data);
let p_id = azul_core::dom::NodeId::new(1);
let Some(p_computed) = cache.computed_values.get(p_id.index()) else {
panic!("p should have computed values");
};
let Some(prop_with_origin) = p_computed.get(&CssPropertyType::FontSize) else {
panic!("p should have computed FontSize property");
};
let CssProperty::FontSize(font_size_value) = &prop_with_origin.property else {
panic!("FontSize should be CssProperty::FontSize variant");
};
let Some(font_size) = font_size_value.get_property() else {
panic!("FontSize value should not be None/Auto/Initial/Inherit");
};
let size = font_size.inner.to_pixels_internal(16.0, 16.0); assert_eq!(
size, 32.0,
"p with font-size: 2em should compute to 32px (2 * 16px)"
);
}
#[test]
fn test_em_unit_cascading_multiplication() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(10.0),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::P)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::em(2.0),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::Span)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::em(1.5),
))]
.into(),
)
.with_child(Dom::create_text("Text")),
),
);
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
cache.compute_inherited_values(node_hierarchy, node_data);
let p_id = azul_core::dom::NodeId::new(1); let span_id = azul_core::dom::NodeId::new(2);
let Some(p_computed) = cache.computed_values.get(p_id.index()) else {
panic!("p should have computed values");
};
let Some(prop_with_origin) = p_computed.get(&CssPropertyType::FontSize) else {
panic!("p should have computed FontSize");
};
let CssProperty::FontSize(p_font_size) = &prop_with_origin.property else {
panic!("FontSize should be CssProperty::FontSize variant");
};
let Some(p_size_val) = p_font_size.get_property() else {
panic!("p FontSize should have value");
};
let p_size = p_size_val.inner.to_pixels_internal(10.0, 10.0); assert_eq!(p_size, 20.0, "p should be 20px (2em * 10px)");
let Some(span_computed) = cache.computed_values.get(span_id.index()) else {
panic!("span should have computed values");
};
let Some(prop_with_origin) = span_computed.get(&CssPropertyType::FontSize) else {
panic!("span should have computed FontSize");
};
let CssProperty::FontSize(span_font_size) = &prop_with_origin.property else {
panic!("span property should be FontSize");
};
let Some(span_size_val) = span_font_size.get_property() else {
panic!("span FontSize should have value");
};
let span_size = span_size_val.inner.to_pixels_internal(20.0, 20.0); assert_eq!(span_size, 30.0, "span should be 30px (1.5em * 20px)");
}
#[test]
fn test_em_on_font_size_refers_to_parent() {
use azul_css::{
css::CssPropertyValue,
props::{
basic::{length::SizeMetric, pixel::PixelValue},
layout::spacing::LayoutPaddingLeft,
},
};
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(20.0),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::P)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::em(1.5),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::Span)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::PaddingLeft(
CssPropertyValue::Exact(LayoutPaddingLeft {
inner: PixelValue::em(2.0),
}),
))]
.into(),
)
.with_child(Dom::create_text("Text")),
),
);
let (styled_dom, cache) = setup_test!(dom);
let div_id = azul_core::dom::NodeId::new(0); let p_id = azul_core::dom::NodeId::new(1); let span_id = azul_core::dom::NodeId::new(2);
let div_computed = cache
.computed_values
.get(div_id.index())
.expect("div should have computed values");
let div_font_prop = div_computed
.get(&CssPropertyType::FontSize)
.expect("div should have FontSize");
let CssProperty::FontSize(div_font_size) = &div_font_prop.property else {
panic!("div property should be FontSize");
};
let div_size_val = div_font_size
.get_property()
.expect("div FontSize should have value");
assert_eq!(
div_size_val.inner.metric,
SizeMetric::Px,
"div should have Px metric"
);
assert_eq!(
div_size_val.inner.number.get(),
20.0,
"div font-size should be 20px"
);
let p_computed = cache
.computed_values
.get(p_id.index())
.expect("p should have computed values");
let p_font_prop = p_computed
.get(&CssPropertyType::FontSize)
.expect("p should have FontSize");
let CssProperty::FontSize(p_font_size) = &p_font_prop.property else {
panic!("p property should be FontSize");
};
let p_size_val = p_font_size
.get_property()
.expect("p FontSize should have value");
assert_eq!(
p_size_val.inner.metric,
SizeMetric::Px,
"p font-size should be resolved to Px metric (was {:?})",
p_size_val.inner.metric
);
assert!(
(p_size_val.inner.number.get() - 30.0).abs() < 0.001,
"p font-size should be 30px (1.5em * 20px), got {}",
p_size_val.inner.number.get()
);
let span_computed = cache
.computed_values
.get(span_id.index())
.expect("span should have computed values");
let span_font_prop = span_computed
.get(&CssPropertyType::FontSize)
.expect("span should have inherited FontSize");
let CssProperty::FontSize(span_font_size) = &span_font_prop.property else {
panic!("span property should be FontSize");
};
let span_size_val = span_font_size
.get_property()
.expect("span FontSize should have value");
println!(
"DEBUG: p font-size: metric={:?}, value={}",
p_size_val.inner.metric,
p_size_val.inner.number.get()
);
println!(
"DEBUG: span font-size: metric={:?}, value={}",
span_size_val.inner.metric,
span_size_val.inner.number.get()
);
println!("DEBUG: span origin={:?}", span_font_prop.origin);
assert_eq!(
span_size_val.inner.metric,
SizeMetric::Px,
"span inherited font-size should be Px metric (was {:?})",
span_size_val.inner.metric
);
assert!(
(span_size_val.inner.number.get() - 30.0).abs() < 0.001,
"span should inherit font-size: 30px from p, got {}",
span_size_val.inner.number.get()
);
}
#[test]
fn test_em_without_ancestor_absolute_unit() {
use azul_css::props::basic::length::SizeMetric;
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::em(2.0),
))]
.into(),
)
.with_child(Dom::create_node(NodeType::P).with_child(Dom::create_text("Text")));
let (styled_dom, cache) = setup_test!(dom);
let div_id = azul_core::dom::NodeId::new(0); let p_id = azul_core::dom::NodeId::new(1);
let div_computed = cache
.computed_values
.get(div_id.index())
.expect("div should have computed values");
let div_font_prop = div_computed
.get(&CssPropertyType::FontSize)
.expect("div should have FontSize");
let CssProperty::FontSize(div_font_size) = &div_font_prop.property else {
panic!("div property should be FontSize");
};
let div_size_val = div_font_size
.get_property()
.expect("div FontSize should have value");
assert_eq!(
div_size_val.inner.metric,
SizeMetric::Px,
"div font-size should be resolved to Px metric (was {:?})",
div_size_val.inner.metric
);
assert!(
(div_size_val.inner.number.get() - 32.0).abs() < 0.001,
"div font-size: 2em without absolute ancestor should be 32px (2 * 16px default), got {}",
div_size_val.inner.number.get()
);
let p_computed = cache
.computed_values
.get(p_id.index())
.expect("p should have computed values");
let p_font_prop = p_computed
.get(&CssPropertyType::FontSize)
.expect("p should have inherited FontSize");
let CssProperty::FontSize(p_font_size) = &p_font_prop.property else {
panic!("p property should be FontSize");
};
let p_size_val = p_font_size
.get_property()
.expect("p FontSize should have value");
assert_eq!(
p_size_val.inner.metric,
SizeMetric::Px,
"p inherited font-size should be Px metric (was {:?})",
p_size_val.inner.metric
);
assert!(
(p_size_val.inner.number.get() - 32.0).abs() < 0.001,
"p should inherit 32px from parent div, got {}",
p_size_val.inner.number.get()
);
}
#[test]
fn test_percentage_font_size_inheritance() {
let dom = Dom::create_div()
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::px(20.0),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::P)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::percent(150.0),
))]
.into(),
)
.with_child(
Dom::create_node(NodeType::Span)
.with_css_props(
vec![CssPropertyWithConditions::simple(CssProperty::font_size(
StyleFontSize::percent(80.0),
))]
.into(),
)
.with_child(Dom::create_text("Text")),
),
);
let (styled_dom, mut cache) = setup_test!(dom);
let node_hierarchy = &styled_dom.node_hierarchy.as_container().internal[..];
let node_data = &styled_dom.node_data.as_container().internal[..];
cache.compute_inherited_values(node_hierarchy, node_data);
let p_id = azul_core::dom::NodeId::new(1); let span_id = azul_core::dom::NodeId::new(2);
let Some(p_computed) = cache.computed_values.get(p_id.index()) else {
panic!("p should have computed values");
};
let Some(prop_with_origin) = p_computed.get(&CssPropertyType::FontSize) else {
panic!("p should have FontSize");
};
let CssProperty::FontSize(p_font_size) = &prop_with_origin.property else {
panic!("p property should be FontSize");
};
let Some(p_size_val) = p_font_size.get_property() else {
panic!("p FontSize should have value");
};
let p_size = p_size_val.inner.to_pixels_internal(20.0, 20.0); assert_eq!(p_size, 30.0, "p with 150% should be 30px (1.5 * 20px)");
let Some(span_computed) = cache.computed_values.get(span_id.index()) else {
panic!("span should have computed values");
};
let Some(prop_with_origin) = span_computed.get(&CssPropertyType::FontSize) else {
panic!("span should have FontSize");
};
let CssProperty::FontSize(span_font_size) = &prop_with_origin.property else {
panic!("span property should be FontSize");
};
let Some(span_size_val) = span_font_size.get_property() else {
panic!("span FontSize should have value");
};
let span_size = span_size_val.inner.to_pixels_internal(30.0, 30.0); assert_eq!(span_size, 24.0, "span with 80% should be 24px (0.8 * 30px)");
}