diff --git a/crates/svg/src/import/css.rs b/crates/svg/src/import/css.rs
index 4db4b22f1..92b61a70a 100644
--- a/crates/svg/src/import/css.rs
+++ b/crates/svg/src/import/css.rs
@@ -485,337 +485,4 @@ impl Styles {
}
#[cfg(test)]
-mod tests {
- use proptest::prelude::*;
-
- use super::*;
-
- #[test]
- fn important_flag_is_case_insensitive() {
- for s in ["fill: red !IMPORTANT", "fill: red !Important", "fill: red ! important"] {
- assert_eq!(parse_decls(s), [("fill".to_string(), "red".to_string(), true)], "{s}");
- }
- assert_eq!(parse_decls("fill: red !importantx"), [("fill".to_string(), "red !importantx".to_string(), false)]);
- }
-
- #[test]
- fn semicolons_in_quotes_and_parens_do_not_split() {
- let d = |k: &str, v: &str| (k.to_string(), v.to_string(), false);
- assert_eq!(parse_decls("font-family:'A;B'; fill:red"), [d("font-family", "'A;B'"), d("fill", "red")]);
- assert_eq!(parse_decls("font-family:\"A;B\"; fill:red"), [d("font-family", "\"A;B\""), d("fill", "red")]);
- assert_eq!(
- parse_decls("fill:url(data:image/svg+xml;base64,AAA); stroke:red"),
- [d("fill", "url(data:image/svg+xml;base64,AAA)"), d("stroke", "red")]
- );
- let _ = parse_decls("font-family:'A;B; fill:red");
- }
-
- fn with_doc(svg: &str, f: impl FnOnce(&roxmltree::Document, &Styles)) {
- let doc = roxmltree::Document::parse(svg).unwrap();
- let st = Styles::new(&doc);
- f(&doc, &st);
- }
-
- fn by_id<'a, 'i>(doc: &'a roxmltree::Document<'i>, id: &str) -> XNode<'a, 'i> {
- doc.descendants().find(|n| n.attribute("id") == Some(id)).unwrap()
- }
-
- #[test]
- fn selectors_and_precedence() {
- with_doc(
- r#""#,
- |doc, st| {
- assert_eq!(st.own(by_id(doc, "t0"), "fill").as_deref(), Some("#111"));
- assert_eq!(st.own(by_id(doc, "t1"), "fill").as_deref(), Some("#333"), "child + class beats descendant");
- assert_eq!(st.own(by_id(doc, "t1"), "stroke").as_deref(), Some("blue"), "multiple classes");
- assert_eq!(st.own(by_id(doc, "t2"), "fill").as_deref(), Some("#666"), "style attribute beats #id");
- assert_eq!(st.own(by_id(doc, "t3"), "fill").as_deref(), Some("#555"), "!important beats style");
- assert_eq!(st.own(by_id(doc, "t3"), "font-weight").as_deref(), Some("bold"));
- assert_eq!(st.own(by_id(doc, "t2"), "stroke"), None, "`.a.b` needs both classes");
- assert_eq!(st.prop(by_id(doc, "t2"), "font-size").as_deref(), Some("9px"));
- },
- );
- }
-
- #[test]
- fn descendant_matching_does_not_backtrack_combinatorially() {
- // Many nested groups share class `a` and the first rule stacks eight descendant
- // `.a` compounds under a leftmost class that matches nothing: an unmemoized
- // matcher enumerates ancestor combinations for it and effectively never
- // finishes (#1703). With suffix states memoized this returns at once, and the
- // matching descendant and mixed child/descendant rules still apply.
- let depth = 48;
- let mut svg = String::from(
- r#"");
- with_doc(&svg, |doc, st| {
- let t = by_id(doc, "t");
- assert_eq!(st.own(t, "fill").as_deref(), Some("#222"), "descendant rule matches, the .missing one does not");
- assert_eq!(st.own(t, "stroke").as_deref(), Some("blue"), "mixed child/descendant rule matches");
- });
- }
-
- #[test]
- fn presentation_attributes_lose_to_rules() {
- with_doc(
- r#""#,
- |doc, st| {
- assert_eq!(st.own(by_id(doc, "a"), "fill").as_deref(), Some("blue"));
- assert_eq!(st.own(by_id(doc, "a"), "stroke").as_deref(), Some("red"));
- },
- );
- }
-
- /// What [`Styles::declared`] gave before the rules were indexed: every rule tested against `n`.
- fn declared_by_every_rule(st: &Styles, n: XNode) -> HashMap {
- type Key = (bool, bool, (u32, u32, u32), usize);
- let mut best: HashMap = HashMap::new();
- let mut put = |name: &str, value: &str, key: Key| {
- if best.get(name).is_none_or(|(k, _)| key >= *k) {
- best.insert(name.to_string(), (key, value.to_string()));
- }
- };
- let mut order = 0;
- for r in st.rules.iter().filter(|r| r.selector.matches(n)) {
- for (k, v, imp) in r.decls.iter() {
- order += 1;
- put(k, v, (*imp, false, r.selector.specificity, order));
- }
- }
- if let Some(s) = n.attribute("style") {
- for (k, v, imp) in parse_decls(s) {
- order += 1;
- put(&k, &v, (imp, true, (0, 0, 0), order));
- }
- }
- best.into_iter().map(|(k, (_, v))| (k, v)).collect()
- }
-
- /// Checks that each element of `svg` gets the properties [`declared_by_every_rule`] gives it.
- fn assert_same_as_every_rule(svg: &str) {
- let doc = roxmltree::Document::parse(svg).unwrap();
- let st = Styles::new(&doc);
- for n in doc.descendants().filter(|n| n.is_element()) {
- assert_eq!(*st.declared(n), declared_by_every_rule(&st, n), "{n:?} in {svg}");
- }
- }
-
- /// Id, class, type, universal and attribute selectors, descendant and child combinators, and
- /// rules that override each other give each element what testing every rule gives it.
- #[test]
- fn indexed_rules_give_what_testing_every_rule_gives() {
- let svg = r#""#;
- assert_same_as_every_rule(svg);
- with_doc(svg, |doc, st| {
- let own = |id: &str, name: &str| st.own(by_id(doc, id), name);
- assert_eq!(own("a", "fill").as_deref(), Some("f2"), "#a beats rect.a");
- assert_eq!(own("r2", "fill").as_deref(), Some("f4"), "rect.a beats g rect");
- assert_eq!(own("r2", "stroke").as_deref(), Some("s5"), "classes apart by a tab and a line feed, one repeated");
- assert_eq!(own("r3", "fill").as_deref(), Some("f8"), "!important");
- assert_eq!(own("r3", "display").as_deref(), Some("none"));
- assert_eq!(own("r4", "fill").as_deref(), Some("f1"), "g rect beats rect");
- assert_eq!(own("r4", "font-size").as_deref(), Some("9px"));
- assert_eq!(own("c5", "fill").as_deref(), Some("f11"), "of rules alike, the last one");
- assert_eq!(own("c5", "stroke").as_deref(), Some("s"));
- assert_eq!(own("t7", "font-size").as_deref(), Some("8px"));
- assert_eq!(own("r8", "fill").as_deref(), Some("f9"));
- });
- }
-
- /// An element's candidates hold each rule once, however often the element repeats a class.
- #[test]
- fn repeated_classes_give_each_rule_once() {
- with_doc(
- r#""#,
- |doc, st| assert_eq!(st.index.candidates(by_id(doc, "r")), [0, 1, 2, 3]),
- );
- }
-
- /// A compound selector over a few tags, ids, classes and attributes.
- fn arb_compound() -> impl Strategy {
- let tag = prop::sample::select(vec!["", "*", "g", "rect", "text", "tspan"]);
- let id = prop::sample::select(vec!["", "#a", "#b"]);
- let classes = prop::collection::vec(prop::sample::select(vec![".a", ".b", ".c"]), 0..3);
- let attr = prop::sample::select(vec!["", "[data-k]", "[data-k=v]"]);
- (tag, id, classes, attr).prop_map(|(t, i, c, a)| match format!("{t}{i}{}{a}", c.concat()) {
- s if s.is_empty() => "*".to_string(),
- s => s,
- })
- }
-
- /// One to three compounds joined by descendant and child combinators.
- fn arb_selector() -> impl Strategy {
- (arb_compound(), prop::collection::vec((prop::sample::select(vec![" ", " > ", ">"]), arb_compound()), 0..3))
- .prop_map(|(first, rest)| rest.into_iter().fold(first, |s, (comb, c)| format!("{s}{comb}{c}")))
- }
-
- /// A rule of one or two selectors that sets one to three properties, some of them `!important`.
- fn arb_rule() -> impl Strategy {
- let decl = (prop::sample::select(vec!["fill", "stroke", "display"]), 0..1000u16, any::())
- .prop_map(|(name, v, important)| format!("{name}: v{v}{}", if important { " !important" } else { "" }));
- (prop::collection::vec(arb_selector(), 1..3), prop::collection::vec(decl, 1..4))
- .prop_map(|(selectors, decls)| format!("{} {{ {} }}", selectors.join(", "), decls.join("; ")))
- }
-
- /// An element that may have an id, classes (some repeated, apart by spaces, tabs and line feeds)
- /// and a `data-k` or `style` attribute, with up to four children on each of the `depth` levels
- /// below it.
- fn arb_element(depth: u32) -> BoxedStrategy {
- let tag = prop::sample::select(vec!["g", "rect", "text", "tspan"]);
- let id = prop::sample::select(vec!["", r#" id="a""#, r#" id="b""#, r#" id="c""#]);
- let classes = prop::collection::vec((prop::sample::select(vec![" ", " ", "
"]), prop::sample::select(vec!["a", "b", "c", "x"])), 0..4)
- .prop_map(|v| match v.iter().map(|(gap, c)| format!("{gap}{c}")).collect::() {
- s if s.is_empty() => s,
- s => format!(r#" class="{s}""#),
- });
- let more = prop::sample::select(vec!["", r#" data-k="v""#, r#" data-k="w""#, r#" style="fill: s""#, r#" style="stroke: s !important""#]);
- let children = if depth == 0 { Just(vec![]).boxed() } else { prop::collection::vec(arb_element(depth - 1), 0..5).boxed() };
- (tag, id, classes, more, children).prop_map(|(t, i, c, m, ch)| format!("<{t}{i}{c}{m}>{}{t}>", ch.concat())).boxed()
- }
-
- proptest! {
- #![proptest_config(ProptestConfig { failure_persistence: None, ..ProptestConfig::default() })]
-
- /// Random style sheets give each element of a random document what testing every rule
- /// gives it.
- #[test]
- fn random_sheets_give_what_testing_every_rule_gives(rules in prop::collection::vec(arb_rule(), 0..24), body in prop::collection::vec(arb_element(3), 1..6)) {
- assert_same_as_every_rule(&format!(r#""#, rules.join("\n"), body.concat()));
- }
- }
-
- /// 50,000 class rules give 50,000 elements, each with one of the classes, their fill within
- /// 20 s.
- #[test]
- fn styling_50000_elements_with_50000_rules_finishes_within_20_s() {
- const N: usize = 50_000;
- let rules: String = (0..N).map(|i| format!(".c{i} {{ fill: #{i:06x} }}\n")).collect();
- let rects: String = (0..N).map(|i| format!(r#""#)).collect();
- let svg = format!(r#""#);
- let (tx, rx) = std::sync::mpsc::channel();
- std::thread::spawn(move || {
- let doc = roxmltree::Document::parse(&svg).unwrap();
- let st = Styles::new(&doc);
- let fills: Vec