Skip to main content

scrape_core/query/
specificity.rs

1//! CSS selector specificity calculation.
2
3/// CSS specificity value (a, b, c) where:
4/// - a = number of ID selectors
5/// - b = number of class selectors, attribute selectors, pseudo-classes
6/// - c = number of type selectors, pseudo-elements
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
8pub struct Specificity {
9    /// Number of ID selectors (#id).
10    pub ids: u32,
11    /// Number of class/attribute/pseudo-class selectors.
12    pub classes: u32,
13    /// Number of type/pseudo-element selectors.
14    pub elements: u32,
15}
16
17impl Specificity {
18    /// Creates a new specificity value.
19    #[must_use]
20    pub const fn new(ids: u32, classes: u32, elements: u32) -> Self {
21        Self { ids, classes, elements }
22    }
23
24    /// Returns the specificity as a single comparable value.
25    /// Higher values indicate higher specificity.
26    #[must_use]
27    pub const fn value(&self) -> u64 {
28        ((self.ids as u64) << 32) | ((self.classes as u64) << 16) | (self.elements as u64)
29    }
30
31    /// Returns a human-readable specificity string like "(0, 2, 1)".
32    #[must_use]
33    pub fn display(&self) -> String {
34        format!("({}, {}, {})", self.ids, self.classes, self.elements)
35    }
36}
37
38impl std::fmt::Display for Specificity {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        write!(f, "{}", self.display())
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn test_specificity_creation() {
50        let spec = Specificity::new(1, 2, 3);
51        assert_eq!(spec.ids, 1);
52        assert_eq!(spec.classes, 2);
53        assert_eq!(spec.elements, 3);
54    }
55
56    #[test]
57    fn test_specificity_value() {
58        let spec1 = Specificity::new(1, 0, 0);
59        let spec2 = Specificity::new(0, 1, 0);
60        let spec3 = Specificity::new(0, 0, 1);
61
62        assert!(spec1.value() > spec2.value());
63        assert!(spec2.value() > spec3.value());
64    }
65
66    #[test]
67    fn test_specificity_comparison() {
68        let spec1 = Specificity::new(1, 0, 0);
69        let spec2 = Specificity::new(0, 100, 0);
70        let spec3 = Specificity::new(0, 0, 100);
71
72        assert!(spec1 > spec2);
73        assert!(spec2 > spec3);
74    }
75
76    #[test]
77    fn test_specificity_display() {
78        let spec = Specificity::new(1, 2, 3);
79        assert_eq!(spec.display(), "(1, 2, 3)");
80        assert_eq!(spec.to_string(), "(1, 2, 3)");
81    }
82
83    #[test]
84    fn test_specificity_default() {
85        let spec = Specificity::default();
86        assert_eq!(spec.ids, 0);
87        assert_eq!(spec.classes, 0);
88        assert_eq!(spec.elements, 0);
89    }
90
91    #[test]
92    fn test_specificity_equality() {
93        let spec1 = Specificity::new(1, 2, 3);
94        let spec2 = Specificity::new(1, 2, 3);
95        let spec3 = Specificity::new(0, 2, 3);
96
97        assert_eq!(spec1, spec2);
98        assert_ne!(spec1, spec3);
99    }
100}