brik/select/
element_impl.rs

1use super::{AttrValue, BrikSelectors, LocalNameSelector, PseudoClass, PseudoElement};
2use crate::attributes::ExpandedName;
3use crate::iter::NodeIterator;
4use crate::node_data_ref::NodeDataRef;
5use crate::tree::{ElementData, Node, NodeData, NodeRef};
6use html5ever::{local_name, ns, LocalName, Namespace};
7use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
8use selectors::{matching, OpaqueElement};
9
10/// The definition of whitespace per CSS Selectors Level 3 ยง 4.
11///
12/// Copied from rust-selectors.
13pub(super) static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C'];
14
15/// Implements selectors::Element for NodeDataRef<ElementData>.
16///
17/// Provides the selectors crate interface for CSS selector matching on
18/// Brik's ElementData nodes. This implementation enables full CSS selector
19/// support including element relationships, attributes, pseudo-classes, and
20/// namespace matching.
21impl selectors::Element for NodeDataRef<ElementData> {
22    type Impl = BrikSelectors;
23
24    #[inline]
25    fn opaque(&self) -> OpaqueElement {
26        let node: &Node = self.as_node();
27        OpaqueElement::new(node)
28    }
29
30    #[inline]
31    fn is_html_slot_element(&self) -> bool {
32        false
33    }
34    #[inline]
35    fn parent_node_is_shadow_root(&self) -> bool {
36        false
37    }
38    #[inline]
39    fn containing_shadow_host(&self) -> Option<Self> {
40        None
41    }
42
43    #[inline]
44    fn parent_element(&self) -> Option<Self> {
45        self.as_node().parent().and_then(NodeRef::into_element_ref)
46    }
47    #[inline]
48    fn prev_sibling_element(&self) -> Option<Self> {
49        self.as_node().preceding_siblings().elements().next()
50    }
51    #[inline]
52    fn next_sibling_element(&self) -> Option<Self> {
53        self.as_node().following_siblings().elements().next()
54    }
55    #[inline]
56    fn first_element_child(&self) -> Option<Self> {
57        self.as_node().children().elements().next()
58    }
59    #[inline]
60    fn is_empty(&self) -> bool {
61        self.as_node().children().all(|child| match *child.data() {
62            NodeData::Element(_) => false,
63            NodeData::Text(ref text) => text.borrow().is_empty(),
64            _ => true,
65        })
66    }
67    #[inline]
68    fn is_root(&self) -> bool {
69        match self.as_node().parent() {
70            None => false,
71            Some(parent) => matches!(*parent.data(), NodeData::Document(_)),
72        }
73    }
74
75    #[inline]
76    fn is_html_element_in_html_document(&self) -> bool {
77        // An element is an HTML element if and only if it's in the HTML namespace.
78        // This includes:
79        // - Standard HTML elements like <div>, <p>, etc.
80        // - Prefixed elements that resolve to the HTML namespace (unusual but valid)
81        // Elements in other namespaces (SVG, MathML, custom) are not HTML elements,
82        // even when embedded in an HTML document.
83        self.name.ns == ns!(html)
84    }
85
86    #[inline]
87    fn has_local_name(&self, name: &LocalName) -> bool {
88        self.name.local == *name
89    }
90    #[inline]
91    fn has_namespace(&self, namespace: &Namespace) -> bool {
92        self.name.ns == *namespace
93    }
94
95    #[inline]
96    fn is_part(&self, _name: &LocalNameSelector) -> bool {
97        false
98    }
99
100    #[inline]
101    fn imported_part(&self, _: &LocalNameSelector) -> Option<LocalNameSelector> {
102        None
103    }
104
105    #[inline]
106    fn is_pseudo_element(&self) -> bool {
107        false
108    }
109
110    #[inline]
111    fn is_same_type(&self, other: &Self) -> bool {
112        self.name == other.name
113    }
114
115    #[inline]
116    fn is_link(&self) -> bool {
117        self.name.ns == ns!(html)
118            && matches!(
119                self.name.local,
120                local_name!("a") | local_name!("area") | local_name!("link")
121            )
122            && self
123                .attributes
124                .borrow()
125                .map
126                .contains_key(&ExpandedName::new(ns!(), local_name!("href")))
127    }
128
129    #[inline]
130    fn has_id(&self, id: &LocalNameSelector, case_sensitivity: CaseSensitivity) -> bool {
131        self.attributes
132            .borrow()
133            .get(local_name!("id"))
134            .is_some_and(|id_attr| case_sensitivity.eq(id.as_bytes(), id_attr.as_bytes()))
135    }
136
137    #[inline]
138    fn has_class(&self, name: &LocalNameSelector, case_sensitivity: CaseSensitivity) -> bool {
139        let name = name.as_bytes();
140        !name.is_empty()
141            && if let Some(class_attr) = self.attributes.borrow().get(local_name!("class")) {
142                class_attr
143                    .split(SELECTOR_WHITESPACE)
144                    .any(|class| case_sensitivity.eq(class.as_bytes(), name))
145            } else {
146                false
147            }
148    }
149
150    #[inline]
151    fn attr_matches(
152        &self,
153        ns: &NamespaceConstraint<&Namespace>,
154        local_name: &LocalNameSelector,
155        operation: &AttrSelectorOperation<&AttrValue>,
156    ) -> bool {
157        let attrs = self.attributes.borrow();
158        match *ns {
159            NamespaceConstraint::Any => attrs.map.iter().any(|(name, attr)| {
160                name.local == **local_name && operation.eval_str(attr.value.as_str())
161            }),
162            NamespaceConstraint::Specific(ns_url) => attrs
163                .map
164                .get(&ExpandedName::new(ns_url, (**local_name).clone()))
165                .is_some_and(|attr| operation.eval_str(attr.value.as_str())),
166        }
167    }
168
169    fn match_pseudo_element(
170        &self,
171        pseudo: &PseudoElement,
172        _context: &mut matching::MatchingContext<BrikSelectors>,
173    ) -> bool {
174        match *pseudo {}
175    }
176
177    fn match_non_ts_pseudo_class(
178        &self,
179        pseudo: &PseudoClass,
180        _context: &mut matching::MatchingContext<BrikSelectors>,
181    ) -> bool {
182        use self::PseudoClass::*;
183        match *pseudo {
184            Active | Focus | Hover | Enabled | Disabled | Checked | Indeterminate | Visited => {
185                false
186            }
187            AnyLink | Link => {
188                self.name.ns == ns!(html)
189                    && matches!(
190                        self.name.local,
191                        local_name!("a") | local_name!("area") | local_name!("link")
192                    )
193                    && self.attributes.borrow().contains(local_name!("href"))
194            }
195        }
196    }
197
198    #[inline]
199    fn apply_selector_flags(&self, _flags: matching::ElementSelectorFlags) {
200        // No-op for static DOM
201    }
202
203    #[inline]
204    fn has_custom_state(&self, _name: &LocalNameSelector) -> bool {
205        // Brik is a static DOM, no custom states
206        false
207    }
208
209    #[inline]
210    fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
211        let _ = filter; // Silence unused warning
212        false
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use crate::html5ever::tendril::TendrilSink;
219    use crate::parse_html;
220    use selectors::Element;
221
222    /// Tests parent_element method.
223    ///
224    /// Verifies that parent_element returns the parent element node.
225    #[test]
226    fn parent_element() {
227        let html = "<div><p><span>text</span></p></div>";
228        let doc = parse_html().one(html);
229        let span = doc.select("span").unwrap().next().unwrap();
230
231        let parent = span.parent_element();
232        assert!(parent.is_some());
233        assert_eq!(parent.unwrap().name.local.as_ref(), "p");
234    }
235
236    /// Tests parent_element with no parent element.
237    ///
238    /// Verifies that parent_element returns None when the parent is not
239    /// an element (e.g., when parent is a document node).
240    #[test]
241    fn parent_element_none() {
242        let doc = parse_html().one("<html></html>");
243        let html = doc.select("html").unwrap().next().unwrap();
244
245        // html element's parent is document, not an element
246        assert!(html.parent_element().is_none());
247    }
248
249    /// Tests prev_sibling_element method.
250    ///
251    /// Verifies that prev_sibling_element returns the previous sibling
252    /// element node.
253    #[test]
254    fn prev_sibling_element() {
255        let html = "<div><p>1</p><span>2</span></div>";
256        let doc = parse_html().one(html);
257        let span = doc.select("span").unwrap().next().unwrap();
258
259        let prev = span.prev_sibling_element();
260        assert!(prev.is_some());
261        assert_eq!(prev.unwrap().name.local.as_ref(), "p");
262    }
263
264    /// Tests prev_sibling_element with no previous sibling.
265    ///
266    /// Verifies that prev_sibling_element returns None when there is no
267    /// previous element sibling.
268    #[test]
269    fn prev_sibling_element_none() {
270        let html = "<div><p>first</p></div>";
271        let doc = parse_html().one(html);
272        let p = doc.select("p").unwrap().next().unwrap();
273
274        assert!(p.prev_sibling_element().is_none());
275    }
276
277    /// Tests next_sibling_element method.
278    ///
279    /// Verifies that next_sibling_element returns the next sibling element node.
280    #[test]
281    fn next_sibling_element() {
282        let html = "<div><p>1</p><span>2</span></div>";
283        let doc = parse_html().one(html);
284        let p = doc.select("p").unwrap().next().unwrap();
285
286        let next = p.next_sibling_element();
287        assert!(next.is_some());
288        assert_eq!(next.unwrap().name.local.as_ref(), "span");
289    }
290
291    /// Tests next_sibling_element with no next sibling.
292    ///
293    /// Verifies that next_sibling_element returns None when there is no
294    /// next element sibling.
295    #[test]
296    fn next_sibling_element_none() {
297        let html = "<div><p>last</p></div>";
298        let doc = parse_html().one(html);
299        let p = doc.select("p").unwrap().next().unwrap();
300
301        assert!(p.next_sibling_element().is_none());
302    }
303
304    /// Tests first_element_child method.
305    ///
306    /// Verifies that first_element_child returns the first child element.
307    #[test]
308    fn first_element_child() {
309        let html = "<div><p>first</p><span>second</span></div>";
310        let doc = parse_html().one(html);
311        let div = doc.select("div").unwrap().next().unwrap();
312
313        let first_child = div.first_element_child();
314        assert!(first_child.is_some());
315        assert_eq!(first_child.unwrap().name.local.as_ref(), "p");
316    }
317
318    /// Tests first_element_child with no children.
319    ///
320    /// Verifies that first_element_child returns None when the element
321    /// has no child elements.
322    #[test]
323    fn first_element_child_none() {
324        let html = "<div></div>";
325        let doc = parse_html().one(html);
326        let div = doc.select("div").unwrap().next().unwrap();
327
328        assert!(div.first_element_child().is_none());
329    }
330
331    /// Tests is_empty with empty element.
332    ///
333    /// Verifies that is_empty returns true for elements with no children.
334    #[test]
335    fn is_empty_true() {
336        let html = "<div></div>";
337        let doc = parse_html().one(html);
338        let div = doc.select("div").unwrap().next().unwrap();
339
340        assert!(div.is_empty());
341    }
342
343    /// Tests is_empty with child element.
344    ///
345    /// Verifies that is_empty returns false when the element contains
346    /// child elements.
347    #[test]
348    fn is_empty_false_with_element() {
349        let html = "<div><p>text</p></div>";
350        let doc = parse_html().one(html);
351        let div = doc.select("div").unwrap().next().unwrap();
352
353        assert!(!div.is_empty());
354    }
355
356    /// Tests is_empty with text content.
357    ///
358    /// Verifies that is_empty returns false when the element contains
359    /// non-empty text nodes.
360    #[test]
361    fn is_empty_false_with_text() {
362        let html = "<div>text</div>";
363        let doc = parse_html().one(html);
364        let div = doc.select("div").unwrap().next().unwrap();
365
366        assert!(!div.is_empty());
367    }
368
369    /// Tests is_empty with empty text nodes.
370    ///
371    /// Verifies that is_empty returns true when text nodes are empty,
372    /// treating them as not contributing to content.
373    #[test]
374    fn is_empty_true_with_empty_text() {
375        let html = "<div></div>";
376        let doc = parse_html().one(html);
377        let div = doc.select("div").unwrap().next().unwrap();
378
379        assert!(div.is_empty());
380    }
381
382    /// Tests is_root with root element.
383    ///
384    /// Verifies that is_root returns true for the document's root element
385    /// (html element whose parent is the document node).
386    #[test]
387    fn is_root_true() {
388        let doc = parse_html().one("<html></html>");
389        let html = doc.select("html").unwrap().next().unwrap();
390
391        assert!(html.is_root());
392    }
393
394    /// Tests is_root with non-root element.
395    ///
396    /// Verifies that is_root returns false for elements that are not
397    /// the document root.
398    #[test]
399    fn is_root_false() {
400        let html = "<html><body><div></div></body></html>";
401        let doc = parse_html().one(html);
402        let div = doc.select("div").unwrap().next().unwrap();
403
404        assert!(!div.is_root());
405    }
406
407    /// Tests is_html_element_in_html_document method.
408    ///
409    /// Verifies that elements in the HTML namespace are correctly identified
410    /// as HTML elements.
411    #[test]
412    fn is_html_element_in_html_document() {
413        let html = "<html><body><div></div></body></html>";
414        let doc = parse_html().one(html);
415        let div = doc.select("div").unwrap().next().unwrap();
416
417        assert!(div.is_html_element_in_html_document());
418    }
419
420    /// Tests has_local_name with matching name.
421    ///
422    /// Verifies that has_local_name returns true when the element's
423    /// local name matches.
424    #[test]
425    fn has_local_name_true() {
426        let html = "<div></div>";
427        let doc = parse_html().one(html);
428        let div = doc.select("div").unwrap().next().unwrap();
429
430        assert!(div.has_local_name(&html5ever::local_name!("div")));
431    }
432
433    /// Tests has_local_name with non-matching name.
434    ///
435    /// Verifies that has_local_name returns false when the element's
436    /// local name does not match.
437    #[test]
438    fn has_local_name_false() {
439        let html = "<div></div>";
440        let doc = parse_html().one(html);
441        let div = doc.select("div").unwrap().next().unwrap();
442
443        assert!(!div.has_local_name(&html5ever::local_name!("span")));
444    }
445
446    /// Tests has_namespace with matching namespace.
447    ///
448    /// Verifies that has_namespace returns true when the element is in
449    /// the specified namespace.
450    #[test]
451    fn has_namespace_true() {
452        let html = "<div></div>";
453        let doc = parse_html().one(html);
454        let div = doc.select("div").unwrap().next().unwrap();
455
456        assert!(div.has_namespace(&html5ever::ns!(html)));
457    }
458
459    /// Tests is_same_type with matching elements.
460    ///
461    /// Verifies that is_same_type returns true for elements with the same
462    /// name and namespace.
463    #[test]
464    fn is_same_type_true() {
465        let html = "<div></div><div></div>";
466        let doc = parse_html().one(html);
467        let mut divs = doc.select("div").unwrap();
468        let div1 = divs.next().unwrap();
469        let div2 = divs.next().unwrap();
470
471        assert!(div1.is_same_type(&div2));
472    }
473
474    /// Tests is_same_type with different elements.
475    ///
476    /// Verifies that is_same_type returns false for elements with different
477    /// names or namespaces.
478    #[test]
479    fn is_same_type_false() {
480        let html = "<div></div><span></span>";
481        let doc = parse_html().one(html);
482        let div = doc.select("div").unwrap().next().unwrap();
483        let span = doc.select("span").unwrap().next().unwrap();
484
485        assert!(!div.is_same_type(&span));
486    }
487
488    /// Tests is_link with anchor element.
489    ///
490    /// Verifies that is_link returns true for <a> elements with href attribute.
491    #[test]
492    fn is_link_true_anchor() {
493        let html = r#"<a href="https://example.com">link</a>"#;
494        let doc = parse_html().one(html);
495        let a = doc.select("a").unwrap().next().unwrap();
496
497        assert!(a.is_link());
498    }
499
500    /// Tests is_link with area element.
501    ///
502    /// Verifies that is_link returns true for <area> elements with href attribute.
503    #[test]
504    fn is_link_true_area() {
505        let html = r#"<map><area href="https://example.com"></map>"#;
506        let doc = parse_html().one(html);
507        let area = doc.select("area").unwrap().next().unwrap();
508
509        assert!(area.is_link());
510    }
511
512    /// Tests is_link with link element.
513    ///
514    /// Verifies that is_link returns true for <link> elements with href attribute.
515    #[test]
516    fn is_link_true_link() {
517        let html = r#"<link href="style.css">"#;
518        let doc = parse_html().one(html);
519        let link = doc.select("link").unwrap().next().unwrap();
520
521        assert!(link.is_link());
522    }
523
524    /// Tests is_link without href attribute.
525    ///
526    /// Verifies that is_link returns false for link elements without
527    /// an href attribute.
528    #[test]
529    fn is_link_false_no_href() {
530        let html = "<a>not a link</a>";
531        let doc = parse_html().one(html);
532        let a = doc.select("a").unwrap().next().unwrap();
533
534        assert!(!a.is_link());
535    }
536
537    /// Tests is_link with non-link element.
538    ///
539    /// Verifies that is_link returns false for elements that are not
540    /// link-type elements (a, area, link).
541    #[test]
542    fn is_link_false_wrong_element() {
543        let html = r#"<div href="https://example.com">not a link</div>"#;
544        let doc = parse_html().one(html);
545        let div = doc.select("div").unwrap().next().unwrap();
546
547        assert!(!div.is_link());
548    }
549
550    /// Tests has_id with case sensitivity.
551    ///
552    /// Verifies that ID selectors match with proper case sensitivity.
553    #[test]
554    fn has_id_case_sensitive() {
555        let html = r#"<div id="myId"></div>"#;
556        let doc = parse_html().one(html);
557
558        assert!(doc.select("#myId").unwrap().next().is_some());
559    }
560
561    /// Tests has_id when ID doesn't match.
562    ///
563    /// Verifies that has_id returns false when the ID doesn't match.
564    #[test]
565    fn has_id_not_found() {
566        let html = r#"<div id="myId"></div>"#;
567        let doc = parse_html().one(html);
568
569        assert!(doc.select("#otherId").unwrap().next().is_none());
570    }
571
572    /// Tests has_class with single class.
573    ///
574    /// Verifies that has_class correctly identifies elements with a
575    /// single class name.
576    #[test]
577    fn has_class_single() {
578        let html = r#"<div class="myClass"></div>"#;
579        let doc = parse_html().one(html);
580
581        assert!(doc.select(".myClass").unwrap().next().is_some());
582    }
583
584    /// Tests has_class with multiple classes.
585    ///
586    /// Verifies that has_class correctly identifies a class in a space-separated
587    /// list of multiple classes.
588    #[test]
589    fn has_class_multiple() {
590        let html = r#"<div class="class1 class2 class3"></div>"#;
591        let doc = parse_html().one(html);
592
593        assert!(doc.select(".class2").unwrap().next().is_some());
594    }
595
596    /// Tests has_class with whitespace in class attribute.
597    ///
598    /// Verifies that has_class correctly handles class attributes with
599    /// various whitespace characters between classes.
600    #[test]
601    fn has_class_with_whitespace() {
602        let html = "<div class=\"class1  \t\n  class2\"></div>";
603        let doc = parse_html().one(html);
604
605        assert!(doc.select(".class2").unwrap().next().is_some());
606    }
607
608    /// Tests has_class when class doesn't match.
609    ///
610    /// Verifies that has_class returns false when the class is not present
611    /// in the element's class attribute.
612    #[test]
613    fn has_class_not_found() {
614        let html = r#"<div class="myClass"></div>"#;
615        let doc = parse_html().one(html);
616
617        assert!(doc.select(".otherClass").unwrap().next().is_none());
618    }
619
620    /// Tests has_class with no class attribute.
621    ///
622    /// Verifies that has_class returns false when the element has no
623    /// class attribute at all.
624    #[test]
625    fn has_class_no_class_attr() {
626        let html = "<div></div>";
627        let doc = parse_html().one(html);
628
629        assert!(doc.select(".myClass").unwrap().next().is_none());
630    }
631
632    /// Tests attr_matches for attribute existence.
633    ///
634    /// Verifies that attribute selectors correctly match elements that
635    /// have the specified attribute, regardless of value.
636    #[test]
637    fn attr_matches_exists() {
638        let html = r#"<div data-value="test"></div>"#;
639        let doc = parse_html().one(html);
640
641        assert!(doc.select("[data-value]").unwrap().next().is_some());
642    }
643
644    /// Tests attr_matches for exact value match.
645    ///
646    /// Verifies that attribute selectors correctly match elements when
647    /// the attribute value exactly equals the specified value.
648    #[test]
649    fn attr_matches_exact_value() {
650        let html = r#"<div data-value="test"></div>"#;
651        let doc = parse_html().one(html);
652
653        assert!(doc
654            .select(r#"[data-value="test"]"#)
655            .unwrap()
656            .next()
657            .is_some());
658    }
659
660    /// Tests attr_matches when value doesn't match.
661    ///
662    /// Verifies that attribute selectors return false when the attribute
663    /// value does not match the specified value.
664    #[test]
665    fn attr_matches_not_found() {
666        let html = r#"<div data-value="test"></div>"#;
667        let doc = parse_html().one(html);
668
669        assert!(doc
670            .select(r#"[data-value="other"]"#)
671            .unwrap()
672            .next()
673            .is_none());
674    }
675
676    /// Tests attr_matches for substring containment.
677    ///
678    /// Verifies that attribute selectors with *= operator correctly match
679    /// when the attribute value contains the specified substring.
680    #[test]
681    fn attr_matches_contains() {
682        let html = r#"<div data-value="hello world"></div>"#;
683        let doc = parse_html().one(html);
684
685        assert!(doc
686            .select(r#"[data-value*="world"]"#)
687            .unwrap()
688            .next()
689            .is_some());
690    }
691
692    /// Tests attr_matches for prefix match.
693    ///
694    /// Verifies that attribute selectors with ^= operator correctly match
695    /// when the attribute value starts with the specified prefix.
696    #[test]
697    fn attr_matches_starts_with() {
698        let html = r#"<div data-value="hello world"></div>"#;
699        let doc = parse_html().one(html);
700
701        assert!(doc
702            .select(r#"[data-value^="hello"]"#)
703            .unwrap()
704            .next()
705            .is_some());
706    }
707
708    /// Tests attr_matches for suffix match.
709    ///
710    /// Verifies that attribute selectors with $= operator correctly match
711    /// when the attribute value ends with the specified suffix.
712    #[test]
713    fn attr_matches_ends_with() {
714        let html = r#"<div data-value="hello world"></div>"#;
715        let doc = parse_html().one(html);
716
717        assert!(doc
718            .select(r#"[data-value$="world"]"#)
719            .unwrap()
720            .next()
721            .is_some());
722    }
723
724    /// Tests is_pseudo_element method.
725    ///
726    /// Verifies that is_pseudo_element returns false since Brik does not
727    /// support pseudo-elements in the static DOM.
728    #[test]
729    fn is_pseudo_element_false() {
730        let html = "<div></div>";
731        let doc = parse_html().one(html);
732        let div = doc.select("div").unwrap().next().unwrap();
733
734        assert!(!div.is_pseudo_element());
735    }
736
737    /// Tests is_html_slot_element method.
738    ///
739    /// Verifies that is_html_slot_element returns false since Brik does not
740    /// support shadow DOM slot elements.
741    #[test]
742    fn is_html_slot_element_false() {
743        let html = "<slot></slot>";
744        let doc = parse_html().one(html);
745        let slot = doc.select("slot").unwrap().next().unwrap();
746
747        assert!(!slot.is_html_slot_element());
748    }
749
750    /// Tests parent_node_is_shadow_root method.
751    ///
752    /// Verifies that parent_node_is_shadow_root returns false since Brik
753    /// does not support shadow DOM.
754    #[test]
755    fn parent_node_is_shadow_root_false() {
756        let html = "<div><p>text</p></div>";
757        let doc = parse_html().one(html);
758        let p = doc.select("p").unwrap().next().unwrap();
759
760        assert!(!p.parent_node_is_shadow_root());
761    }
762
763    /// Tests containing_shadow_host method.
764    ///
765    /// Verifies that containing_shadow_host returns None since Brik does
766    /// not support shadow DOM.
767    #[test]
768    fn containing_shadow_host_none() {
769        let html = "<div></div>";
770        let doc = parse_html().one(html);
771        let div = doc.select("div").unwrap().next().unwrap();
772
773        assert!(div.containing_shadow_host().is_none());
774    }
775
776    /// Tests is_part method.
777    ///
778    /// Verifies that is_part returns false since Brik does not support
779    /// shadow DOM parts.
780    #[test]
781    fn is_part_false() {
782        let html = "<div></div>";
783        let doc = parse_html().one(html);
784        let div = doc.select("div").unwrap().next().unwrap();
785
786        assert!(!div.is_part(&html5ever::local_name!("div").into()));
787    }
788
789    /// Tests imported_part method.
790    ///
791    /// Verifies that imported_part returns None since Brik does not support
792    /// shadow DOM parts.
793    #[test]
794    fn imported_part_none() {
795        let html = "<div></div>";
796        let doc = parse_html().one(html);
797        let div = doc.select("div").unwrap().next().unwrap();
798
799        assert!(div
800            .imported_part(&html5ever::local_name!("div").into())
801            .is_none());
802    }
803
804    /// Tests has_custom_state method.
805    ///
806    /// Verifies that has_custom_state returns false since Brik has a static
807    /// DOM and does not support custom element states.
808    #[test]
809    fn has_custom_state_false() {
810        let html = "<div></div>";
811        let doc = parse_html().one(html);
812        let div = doc.select("div").unwrap().next().unwrap();
813
814        assert!(!div.has_custom_state(&html5ever::local_name!("div").into()));
815    }
816
817    /// Tests :link pseudo-class selector.
818    ///
819    /// Verifies that the :link pseudo-class matches anchor elements with href
820    /// attribute through the pseudo-class matching path.
821    #[test]
822    fn pseudo_class_link() {
823        let html = r#"<a href="https://example.com">link</a><a>no href</a>"#;
824        let doc = parse_html().one(html);
825
826        let links: Vec<_> = doc.select("a:link").unwrap().collect();
827        assert_eq!(links.len(), 1);
828    }
829
830    /// Tests :any-link pseudo-class selector.
831    ///
832    /// Verifies that the :any-link pseudo-class matches link-type elements
833    /// (a, area, link) with href attribute.
834    #[test]
835    fn pseudo_class_any_link() {
836        let html = r#"<a href="https://example.com">link</a><link href="style.css">"#;
837        let doc = parse_html().one(html);
838
839        let links: Vec<_> = doc.select(":any-link").unwrap().collect();
840        assert_eq!(links.len(), 2);
841    }
842
843    /// Tests has_namespace with non-matching namespace.
844    ///
845    /// Verifies that has_namespace returns false when element is not in
846    /// the specified namespace.
847    #[test]
848    fn has_namespace_false() {
849        let html = "<div></div>";
850        let doc = parse_html().one(html);
851        let div = doc.select("div").unwrap().next().unwrap();
852
853        // HTML element should not match SVG namespace.
854        assert!(!div.has_namespace(&html5ever::ns!(svg)));
855    }
856
857    /// Tests attr_matches with namespace wildcard in selector.
858    ///
859    /// Verifies that attribute matching works when iterating through
860    /// all namespaces (NamespaceConstraint::Any).
861    #[test]
862    fn attr_matches_any_namespace_constraint() {
863        let html = r#"<div data-value="test"></div>"#;
864        let doc = parse_html().one(html);
865        let div = doc.select("div").unwrap().next().unwrap();
866
867        // Test attribute exists regardless of namespace.
868        let attrs = div.attributes.borrow();
869        assert!(attrs.contains("data-value"));
870    }
871}