Skip to main content

react_rs_elements/
head.rs

1#[derive(Debug, Clone, Default)]
2pub struct Head {
3    pub title: Option<String>,
4    pub meta_tags: Vec<MetaTag>,
5    pub links: Vec<LinkTag>,
6}
7
8#[derive(Debug, Clone)]
9pub struct MetaTag {
10    pub name: String,
11    pub content: String,
12}
13
14#[derive(Debug, Clone)]
15pub struct LinkTag {
16    pub rel: String,
17    pub href: String,
18}
19
20impl Head {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    pub fn title(mut self, title: impl Into<String>) -> Self {
26        self.title = Some(title.into());
27        self
28    }
29
30    pub fn meta(mut self, name: impl Into<String>, content: impl Into<String>) -> Self {
31        self.meta_tags.push(MetaTag {
32            name: name.into(),
33            content: content.into(),
34        });
35        self
36    }
37
38    pub fn description(self, desc: impl Into<String>) -> Self {
39        self.meta("description", desc)
40    }
41
42    pub fn keywords(self, keywords: impl Into<String>) -> Self {
43        self.meta("keywords", keywords)
44    }
45
46    pub fn og_title(self, title: impl Into<String>) -> Self {
47        self.meta("og:title", title)
48    }
49
50    pub fn og_description(self, desc: impl Into<String>) -> Self {
51        self.meta("og:description", desc)
52    }
53
54    pub fn og_image(self, url: impl Into<String>) -> Self {
55        self.meta("og:image", url)
56    }
57
58    pub fn link_stylesheet(mut self, href: impl Into<String>) -> Self {
59        self.links.push(LinkTag {
60            rel: "stylesheet".to_string(),
61            href: href.into(),
62        });
63        self
64    }
65
66    pub fn link(mut self, rel: impl Into<String>, href: impl Into<String>) -> Self {
67        self.links.push(LinkTag {
68            rel: rel.into(),
69            href: href.into(),
70        });
71        self
72    }
73
74    pub fn to_html(&self) -> String {
75        let mut parts = Vec::new();
76        if let Some(title) = &self.title {
77            parts.push(format!("<title>{}</title>", title));
78        }
79        for meta in &self.meta_tags {
80            parts.push(format!(
81                "<meta name=\"{}\" content=\"{}\">",
82                meta.name, meta.content
83            ));
84        }
85        for link in &self.links {
86            parts.push(format!(
87                "<link rel=\"{}\" href=\"{}\">",
88                link.rel, link.href
89            ));
90        }
91        parts.join("\n    ")
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_head_title() {
101        let head = Head::new().title("My App");
102        assert_eq!(head.title, Some("My App".to_string()));
103        assert!(head.to_html().contains("<title>My App</title>"));
104    }
105
106    #[test]
107    fn test_head_meta() {
108        let head = Head::new().description("A great app").keywords("rust, web");
109        assert_eq!(head.meta_tags.len(), 2);
110        let html = head.to_html();
111        assert!(html.contains("description"));
112        assert!(html.contains("A great app"));
113    }
114
115    #[test]
116    fn test_head_og() {
117        let head = Head::new()
118            .og_title("My Page")
119            .og_description("Page desc")
120            .og_image("https://example.com/img.png");
121        assert_eq!(head.meta_tags.len(), 3);
122    }
123
124    #[test]
125    fn test_head_stylesheet() {
126        let head = Head::new().link_stylesheet("/styles.css");
127        let html = head.to_html();
128        assert!(html.contains("<link rel=\"stylesheet\" href=\"/styles.css\">"));
129    }
130
131    #[test]
132    fn test_head_empty() {
133        let head = Head::new();
134        assert_eq!(head.to_html(), "");
135    }
136
137    #[test]
138    fn test_head_full() {
139        let head = Head::new()
140            .title("next.rs")
141            .description("Rust web framework")
142            .link_stylesheet("/app.css");
143        let html = head.to_html();
144        assert!(html.contains("<title>next.rs</title>"));
145        assert!(html.contains("description"));
146        assert!(html.contains("stylesheet"));
147    }
148}