react_rs_elements/
head.rs1#[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}