tailwind_rs_testing/
mock_components.rs

1//! Mock components for testing tailwind-rs
2
3use std::collections::HashMap;
4
5/// A mock component for testing
6#[derive(Debug, Clone)]
7pub struct MockComponent {
8    pub name: String,
9    pub html: String,
10    pub classes: std::collections::HashSet<String>,
11    pub custom_properties: HashMap<String, String>,
12    pub props: HashMap<String, String>,
13}
14
15impl MockComponent {
16    /// Create a new mock component
17    pub fn new(name: impl Into<String>) -> Self {
18        Self {
19            name: name.into(),
20            html: String::new(),
21            classes: std::collections::HashSet::new(),
22            custom_properties: HashMap::new(),
23            props: HashMap::new(),
24        }
25    }
26
27    /// Set the HTML content
28    pub fn with_html(mut self, html: impl Into<String>) -> Self {
29        self.html = html.into();
30        self
31    }
32
33    /// Add a CSS class
34    pub fn with_class(mut self, class: impl Into<String>) -> Self {
35        self.classes.insert(class.into());
36        self
37    }
38
39    /// Add multiple CSS classes
40    pub fn with_classes(mut self, classes: impl IntoIterator<Item = String>) -> Self {
41        for class in classes {
42            self.classes.insert(class);
43        }
44        self
45    }
46
47    /// Add a custom property
48    pub fn with_custom_property(
49        mut self,
50        property: impl Into<String>,
51        value: impl Into<String>,
52    ) -> Self {
53        self.custom_properties.insert(property.into(), value.into());
54        self
55    }
56
57    /// Add a prop
58    pub fn with_prop(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
59        self.props.insert(key.into(), value.into());
60        self
61    }
62
63    /// Generate the final HTML
64    pub fn to_html(&self) -> String {
65        let mut html = self.html.clone();
66
67        // If the HTML contains placeholder tokens, use the old behavior
68        if html.contains("{{classes}}") || html.contains("{{style}}") {
69            if !self.classes.is_empty() {
70                let mut classes_vec: Vec<String> = self.classes.iter().cloned().collect();
71                classes_vec.sort(); // Ensure deterministic ordering
72                let classes_str = classes_vec.join(" ");
73                html = html.replace("{{classes}}", &format!("class=\"{}\"", classes_str));
74            }
75
76            if !self.custom_properties.is_empty() {
77                let style_parts: Vec<String> = self
78                    .custom_properties
79                    .iter()
80                    .map(|(k, v)| format!("--{}: {}", k, v))
81                    .collect();
82                let style_str = format!("style=\"{}\"", style_parts.join("; "));
83                html = html.replace("{{style}}", &style_str);
84            }
85
86            for (key, value) in &self.props {
87                html = html.replace(&format!("{{{{{}}}}}", key), value);
88            }
89        } else {
90            // If no placeholder tokens, wrap the content with classes and styles
91            let mut attributes = Vec::new();
92            
93            if !self.classes.is_empty() {
94                let mut classes_vec: Vec<String> = self.classes.iter().cloned().collect();
95                classes_vec.sort(); // Ensure deterministic ordering
96                let classes_str = classes_vec.join(" ");
97                attributes.push(format!("class=\"{}\"", classes_str));
98            }
99
100            if !self.custom_properties.is_empty() {
101                let style_parts: Vec<String> = self
102                    .custom_properties
103                    .iter()
104                    .map(|(k, v)| format!("--{}: {}", k, v))
105                    .collect();
106                attributes.push(format!("style=\"{}\"", style_parts.join("; ")));
107            }
108
109            // Replace props in the HTML content
110            let mut processed_html = html;
111            for (key, value) in &self.props {
112                processed_html = processed_html.replace(&format!("{{{{{}}}}}", key), value);
113            }
114
115            if !attributes.is_empty() {
116                // Wrap the content in a div with the attributes
117                html = format!("<div {}>{}</div>", attributes.join(" "), processed_html);
118            } else {
119                html = processed_html;
120            }
121        }
122
123        html
124    }
125}
126
127/// Create a mock button component
128pub fn create_mock_button() -> MockComponent {
129    MockComponent::new("button")
130        .with_html("<button {{classes}} {{style}}>{{text}}</button>")
131        .with_class("bg-blue-500")
132        .with_class("text-white")
133        .with_class("px-4")
134        .with_class("py-2")
135        .with_class("rounded")
136        .with_prop("text", "Click me")
137}
138
139/// Create a mock card component
140pub fn create_mock_card() -> MockComponent {
141    MockComponent::new("card")
142        .with_html("<div {{classes}} {{style}}>{{content}}</div>")
143        .with_class("bg-white")
144        .with_class("shadow-lg")
145        .with_class("rounded-lg")
146        .with_class("p-6")
147        .with_prop("content", "Card content")
148}
149
150/// Create a mock input component
151pub fn create_mock_input() -> MockComponent {
152    MockComponent::new("input")
153        .with_html("<input {{classes}} {{style}} placeholder=\"{{placeholder}}\" />")
154        .with_class("w-full")
155        .with_class("px-3")
156        .with_class("py-2")
157        .with_class("border")
158        .with_class("border-gray-300")
159        .with_class("rounded-md")
160        .with_prop("placeholder", "Enter text")
161}
162
163/// Create a mock responsive grid component
164pub fn create_mock_responsive_grid() -> MockComponent {
165    MockComponent::new("responsive_grid")
166        .with_html("<div {{classes}} {{style}}>{{items}}</div>")
167        .with_class("grid")
168        .with_class("gap-4")
169        .with_class("sm:grid-cols-1")
170        .with_class("md:grid-cols-2")
171        .with_class("lg:grid-cols-3")
172        .with_prop("items", "Grid items")
173}
174
175/// Create a mock themed component
176pub fn create_mock_themed_component() -> MockComponent {
177    MockComponent::new("themed_component")
178        .with_html("<div {{classes}} {{style}}>{{content}}</div>")
179        .with_class("p-4")
180        .with_class("rounded-lg")
181        .with_class("border")
182        .with_custom_property("primary-color", "#3b82f6")
183        .with_custom_property("spacing", "1rem")
184        .with_prop("content", "Themed content")
185}
186
187/// Create a mock component with custom configuration
188pub fn create_mock_component(name: impl Into<String>) -> MockComponent {
189    MockComponent::new(name)
190}
191
192/// Create a mock component for testing specific scenarios
193pub fn create_mock_component_for_test(test_name: &str) -> MockComponent {
194    match test_name {
195        "button" => create_mock_button(),
196        "card" => create_mock_card(),
197        "input" => create_mock_input(),
198        "responsive_grid" => create_mock_responsive_grid(),
199        "themed_component" => create_mock_themed_component(),
200        _ => create_mock_component(test_name),
201    }
202}
203
204/// Create multiple mock components for testing
205pub fn create_mock_components() -> Vec<MockComponent> {
206    vec![
207        create_mock_button(),
208        create_mock_card(),
209        create_mock_input(),
210        create_mock_responsive_grid(),
211        create_mock_themed_component(),
212    ]
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_mock_component_creation() {
221        let component = MockComponent::new("test");
222        assert_eq!(component.name, "test");
223        assert!(component.html.is_empty());
224        assert!(component.classes.is_empty());
225        assert!(component.custom_properties.is_empty());
226        assert!(component.props.is_empty());
227    }
228
229    #[test]
230    fn test_mock_component_with_html() {
231        let component = MockComponent::new("test").with_html("<div>Test</div>");
232        assert_eq!(component.html, "<div>Test</div>");
233    }
234
235    #[test]
236    fn test_mock_component_with_classes() {
237        let component = MockComponent::new("test")
238            .with_class("bg-blue-500")
239            .with_class("text-white");
240
241        assert!(component.classes.contains("bg-blue-500"));
242        assert!(component.classes.contains("text-white"));
243    }
244
245    #[test]
246    fn test_mock_component_with_custom_properties() {
247        let component = MockComponent::new("test")
248            .with_custom_property("primary-color", "#3b82f6")
249            .with_custom_property("spacing", "1rem");
250
251        assert_eq!(
252            component.custom_properties.get("primary-color"),
253            Some(&"#3b82f6".to_string())
254        );
255        assert_eq!(
256            component.custom_properties.get("spacing"),
257            Some(&"1rem".to_string())
258        );
259    }
260
261    #[test]
262    fn test_mock_component_with_props() {
263        let component = MockComponent::new("test")
264            .with_prop("text", "Hello World")
265            .with_prop("count", "42");
266
267        assert_eq!(
268            component.props.get("text"),
269            Some(&"Hello World".to_string())
270        );
271        assert_eq!(component.props.get("count"), Some(&"42".to_string()));
272    }
273
274    #[test]
275    fn test_mock_component_to_html() {
276        let component = MockComponent::new("test")
277            .with_html("<button {{classes}} {{style}}>{{text}}</button>")
278            .with_class("bg-blue-500")
279            .with_class("text-white")
280            .with_custom_property("primary-color", "#3b82f6")
281            .with_prop("text", "Click me");
282
283        let html = component.to_html();
284        assert!(html.contains("class=\"bg-blue-500 text-white\""));
285        assert!(html.contains("style=\"--primary-color: #3b82f6\""));
286        assert!(html.contains("Click me"));
287    }
288
289    #[test]
290    fn test_mock_button_component() {
291        let button = create_mock_button();
292        let html = button.to_html();
293
294        assert!(html.contains("<button"));
295        assert!(html.contains("bg-blue-500"));
296        assert!(html.contains("text-white"));
297        assert!(html.contains("Click me"));
298    }
299
300    #[test]
301    fn test_mock_card_component() {
302        let card = create_mock_card();
303        let html = card.to_html();
304
305        assert!(html.contains("<div"));
306        assert!(html.contains("bg-white"));
307        assert!(html.contains("shadow-lg"));
308        assert!(html.contains("Card content"));
309    }
310
311    #[test]
312    fn test_mock_input_component() {
313        let input = create_mock_input();
314        let html = input.to_html();
315
316        assert!(html.contains("<input"));
317        assert!(html.contains("w-full"));
318        assert!(html.contains("border"));
319        assert!(html.contains("Enter text"));
320    }
321
322    #[test]
323    fn test_mock_responsive_grid_component() {
324        let grid = create_mock_responsive_grid();
325        let html = grid.to_html();
326
327        assert!(html.contains("<div"));
328        assert!(html.contains("grid"));
329        assert!(html.contains("sm:grid-cols-1"));
330        assert!(html.contains("md:grid-cols-2"));
331        assert!(html.contains("lg:grid-cols-3"));
332    }
333
334    #[test]
335    fn test_mock_themed_component() {
336        let themed = create_mock_themed_component();
337        let html = themed.to_html();
338
339        assert!(html.contains("<div"));
340        assert!(html.contains("p-4"));
341        assert!(html.contains("rounded-lg"));
342        assert!(html.contains("--primary-color: #3b82f6"));
343        assert!(html.contains("--spacing: 1rem"));
344    }
345
346    #[test]
347    fn test_create_mock_components() {
348        let components = create_mock_components();
349        assert_eq!(components.len(), 5);
350
351        let names: Vec<String> = components.iter().map(|c| c.name.clone()).collect();
352        assert!(names.contains(&"button".to_string()));
353        assert!(names.contains(&"card".to_string()));
354        assert!(names.contains(&"input".to_string()));
355        assert!(names.contains(&"responsive_grid".to_string()));
356        assert!(names.contains(&"themed_component".to_string()));
357    }
358}