use velox_dom::{Props, VNode, h};
use velox_style::{Stylesheet, apply_with_cascade};
fn cascaded(css: &str, class: &str, prop: &str) -> String {
let sheet = Stylesheet::parse(css);
let node = h("div", Props::new().set("class", class), vec![]);
let styled = apply_with_cascade(&node, &sheet);
let VNode::Element { props, .. } = &styled else {
panic!("expected element, got {styled:?}");
};
let style = props
.attrs
.get("style")
.unwrap_or_else(|| panic!("cascade wrote no style attr for .{class}"));
style
.split(';')
.filter_map(|decl| decl.trim().split_once(':'))
.find(|(k, _)| k.trim() == prop)
.map(|(_, v)| v.trim().to_string())
.unwrap_or_else(|| panic!("no `{prop}` in cascaded style: {style:?}"))
}
#[test]
fn rgba_arguments_survive_a_class_rule() {
let v = cascaded(
".scrim { background: rgba(20, 24, 27, 0.34); }",
"scrim",
"background",
);
assert_eq!(
v, "rgba(20, 24, 27, 0.34)",
"rgba() arguments were eaten by the declaration parser"
);
}
#[test]
fn rgb_arguments_survive_a_class_rule() {
let v = cascaded(".ok { color: rgb(34, 197, 94); }", "ok", "color");
assert_eq!(
v, "rgb(34, 197, 94)",
"rgb() arguments were eaten by the declaration parser"
);
}
#[test]
fn calc_arguments_survive_a_class_rule() {
let v = cascaded(".w { width: calc(100% - 8px); }", "w", "width");
assert_eq!(
v, "calc(100% - 8px)",
"calc() arguments were eaten by the declaration parser"
);
}
#[test]
fn var_arguments_survive_a_class_rule() {
let v = cascaded(".brand { color: var(--brand); }", "brand", "color");
assert_eq!(
v, "var(--brand)",
"var() arguments were eaten by the declaration parser"
);
}
#[test]
fn nested_function_arguments_survive_intact() {
let v = cascaded(
".mix { background: rgba(calc(1 + 2), 0, 0, 1); }",
"mix",
"background",
);
assert_eq!(
v, "rgba(calc(1 + 2), 0, 0, 1)",
"a function nested inside a function did not round-trip"
);
}
#[test]
fn three_levels_of_nesting_survive_intact() {
let v = cascaded(
".deep { box-shadow: 0 2px 8px rgba(calc(0 + 1), 2, 3, var(--a)); }",
"deep",
"box-shadow",
);
assert_eq!(
v, "0 2px 8px rgba(calc(0 + 1), 2, 3, var(--a))",
"three levels of function nesting did not round-trip"
);
}
#[test]
fn bare_parenthesis_block_is_not_truncated() {
let v = cascaded(".p { width: (1 + 2); }", "p", "width");
assert_eq!(
v, "(1 + 2)",
"a bare parenthesis block was truncated to `(`"
);
}
#[test]
fn cascaded_rgba_is_parseable_by_the_renderer_s_colour_reader() {
let v = cascaded(
".scrim { background: rgba(20, 24, 27, 0.34); }",
"scrim",
"background",
);
let c = velox_dom::style::Color::parse(&v)
.unwrap_or_else(|| panic!("Color::parse rejected the cascaded value {v:?}"));
assert_eq!((c.r, c.g, c.b), (20, 24, 27));
assert_eq!(c.a, 86, "alpha 0.34 should survive as 86/255");
}
#[test]
fn cascaded_tight_rgba_is_parseable_too() {
let v = cascaded(
".add { background: rgba(0,0,255,0.5); }",
"add",
"background",
);
assert_eq!(v, "rgba(0,0,255,0.5)");
let c = velox_dom::style::Color::parse(&v).expect("tight rgba must parse");
assert_eq!((c.r, c.g, c.b, c.a), (0, 0, 255, 127));
}
#[test]
fn a_value_with_no_function_is_unchanged() {
let v = cascaded(
".btn { transition: transform 0.2s ease; }",
"btn",
"transition",
);
assert_eq!(v, "transform 0.2s ease");
}
#[test]
fn a_function_after_other_tokens_does_not_eat_them() {
let v = cascaded(
".b { border: 1px solid rgba(0, 0, 0, 0.1); }",
"b",
"border",
);
assert_eq!(v, "1px solid rgba(0, 0, 0, 0.1)");
}
#[test]
fn a_multi_value_inset_survives_a_class_rule() {
let v = cascaded(
".card { clip-path: inset(10px 20px); }",
"card",
"clip-path",
);
assert_eq!(v, "inset(10px 20px)");
}
#[test]
fn a_two_function_filter_list_survives_a_class_rule() {
let v = cascaded(
".thumb { img-filter: blur(4px) brightness(1.2); }",
"thumb",
"img-filter",
);
assert_eq!(v, "blur(4px) brightness(1.2)");
}
#[test]
fn calc_and_var_are_stored_but_still_not_resolved() {
assert_eq!(
cascaded(".w { width: calc(100% - 8px); }", "w", "width"),
"calc(100% - 8px)"
);
assert!(
velox_dom::style::Length::parse("calc(100% - 8px)").is_none(),
"if calc() now resolves, this test is stale: PARSE it and update it"
);
}