sunbeam_ir/
generate_css_file.rs

1use crate::{Classes, RetrievedClassDefinition};
2use std::collections::HashSet;
3
4impl Classes {
5    /// Write the classes to a String that can be written to a CSS file.
6    pub fn to_css_file_contents(&self) -> String {
7        let classes: HashSet<&RetrievedClassDefinition> = self.classes.iter().collect();
8        let mut classes: Vec<&RetrievedClassDefinition> = classes.into_iter().collect();
9        classes.sort_by(|a, b| {
10            let a_hover = a.modifiers.hover();
11            let b_hover = b.modifiers.hover();
12
13            if a_hover || b_hover {
14                return a_hover.cmp(&b_hover);
15            }
16
17            a.css_class_definition.cmp(&b.css_class_definition)
18        });
19
20        let mut class_file_contents = "".to_string();
21
22        for class in classes {
23            class_file_contents += &class.css_class_definition;
24            class_file_contents += "\n";
25        }
26
27        class_file_contents.trim().to_string()
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::{Modifiers, RetrievedClassDefinition};
35
36    /// Verify that we sort the classes in our generated CSS file.
37    #[test]
38    fn write_css_sorted() {
39        let classes = Classes {
40            classes: vec![
41                RetrievedClassDefinition::new(
42                    "mb10".to_string(),
43                    r#".mb10 {
44    margin-bottom: 10px;
45}"#
46                    .to_string(),
47                    Modifiers::default(),
48                ),
49                RetrievedClassDefinition::new(
50                    "mt5".to_string(),
51                    r#".mt5 {
52    margin-top: 5px;
53}"#
54                    .to_string(),
55                    Modifiers::default(),
56                ),
57                RetrievedClassDefinition::new(
58                    "mb2".to_string(),
59                    r#".mb2 {
60    margin-bottom: 2px;
61}"#
62                    .to_string(),
63                    Modifiers::default(),
64                ),
65            ],
66        };
67
68        let contents = classes.to_css_file_contents();
69
70        assert_eq!(
71            contents,
72            r#"
73.mb10 {
74    margin-bottom: 10px;
75}
76.mb2 {
77    margin-bottom: 2px;
78}
79.mt5 {
80    margin-top: 5px;
81}
82"#
83            .trim()
84        );
85    }
86
87    /// Verify that we do not include duplicate copies of a class.
88    #[test]
89    fn does_not_duplicate() {
90        let classes = Classes {
91            classes: vec![
92                RetrievedClassDefinition::new(
93                    "mb10".to_string(),
94                    r#".mb10 {
95    margin-bottom: 10px;
96}"#
97                    .to_string(),
98                    Modifiers::default(),
99                ),
100                RetrievedClassDefinition::new(
101                    "mb10".to_string(),
102                    r#".mb10 {
103    margin-bottom: 10px;
104}"#
105                    .to_string(),
106                    Modifiers::default(),
107                ),
108            ],
109        };
110
111        let contents = classes.to_css_file_contents();
112
113        assert_eq!(
114            contents,
115            r#"
116.mb10 {
117    margin-bottom: 10px;
118}
119"#
120            .trim()
121        );
122    }
123
124    /// Verify all hover: classes come last in the generated CSS, since you'll typically want your
125    /// hover styles to take precedence over other styles.
126    #[test]
127    fn hover_comes_before_visited() {
128        let mut mod1 = Modifiers::default();
129        mod1.set_hover_enabled().unwrap();
130        let mod1 = (5, mod1);
131
132        let mod2 = Modifiers::default();
133        let mod2 = (10, mod2);
134
135        for (a, b) in vec![(mod1, mod2), (mod2, mod1)] {
136            let classes = Classes {
137                classes: vec![
138                    RetrievedClassDefinition::new(
139                        "".to_string(),
140                        format!(
141                            r#".mb{size} {{
142    margin-bottom: {size}px;
143}}"#,
144                            size = a.0
145                        ),
146                        a.1,
147                    ),
148                    RetrievedClassDefinition::new(
149                        "".to_string(),
150                        format!(
151                            r#".mb{size} {{
152    margin-bottom: {size}px;
153}}"#,
154                            size = b.0
155                        ),
156                        b.1,
157                    ),
158                ],
159            };
160
161            let contents = classes.to_css_file_contents();
162            assert!(
163                contents.find("margin-bottom: 5px").unwrap()
164                    > contents.find("margin-bottom: 10px").unwrap()
165            )
166        }
167    }
168}