//! Component utilities using trait-based dependency injection use crate::*; /// Basic component renderer implementation /// /// This provides a simple implementation of ComponentRenderer that can be /// used as a starting point for implementations. pub struct BasicComponentRenderer { _phantom: std::marker::PhantomData, } impl BasicComponentRenderer { /// Create new basic component renderer pub fn new() -> Self { Self { _phantom: std::marker::PhantomData, } } } impl Default for BasicComponentRenderer { fn default() -> Self { Self::new() } } /// Simple layout provider using configuration /// /// This provides basic layout functionality that implementations can extend. pub struct ConfigurableLayoutProvider { /// Available themes pub themes: Vec, /// Default theme ID pub default_theme_id: String, } impl ConfigurableLayoutProvider { /// Create layout provider from site configuration pub fn from_site_config(site_config: &SiteConfig) -> Self { Self { themes: site_config.themes.clone(), default_theme_id: site_config.default_theme.clone(), } } /// Create layout provider with explicit configuration pub fn new(themes: Vec, default_theme_id: String) -> Self { Self { themes, default_theme_id, } } } /// Simple layout wrapper #[derive(Debug, Clone)] pub struct BasicLayout { /// Layout identifier pub id: String, /// Layout template (implementation-specific) pub template: String, } impl BasicLayout { /// Create new basic layout pub fn new(id: impl Into, template: impl Into) -> Self { Self { id: id.into(), template: template.into(), } } } impl LayoutProvider for ConfigurableLayoutProvider { type Layout = BasicLayout; type Theme = ThemeConfig; type View = String; // Implementations should use their actual view type fn get_layout(&self, layout_id: &str) -> ComponentResult { // Simple layout mapping - implementations can make this more sophisticated let template = match layout_id { "default" => "{title}{content}", "minimal" => "{content}", "blog" => "{title}
{content}
", _ => return Err(ComponentError::LayoutError(format!("Unknown layout: {}", layout_id))), }; Ok(BasicLayout::new(layout_id, template)) } fn get_theme(&self, theme_id: &str) -> ComponentResult { self.themes .iter() .find(|theme| theme.id == theme_id) .cloned() .ok_or_else(|| ComponentError::LayoutError(format!("Theme not found: {}", theme_id))) } fn apply_layout( &self, layout: &Self::Layout, view: Self::View, theme: &Self::Theme, _language: &str, ) -> ComponentResult { // Simple template substitution - implementations should use proper templating let mut result = layout.template.clone(); result = result.replace("{content}", &view); result = result.replace("{title}", "Page Title"); // Implementation should provide real title // Apply theme variables for (var, value) in &theme.css_variables { result = result.replace(&format!("{{{}}}", var), value); } Ok(result) } fn default_layout(&self) -> ComponentResult { self.get_layout("default") } fn default_theme(&self) -> ComponentResult { self.get_theme(&self.default_theme_id) } } #[cfg(test)] mod tests { use super::*; use std::collections::HashMap; fn create_test_themes() -> Vec { vec![ ThemeConfig { id: "default".to_string(), name: "Default Theme".to_string(), description: Some("Default theme".to_string()), primary_color: "#007acc".to_string(), secondary_color: "#ffffff".to_string(), css_variables: { let mut vars = HashMap::new(); vars.insert("primary".to_string(), "#007acc".to_string()); vars.insert("secondary".to_string(), "#ffffff".to_string()); vars }, is_dark: false, }, ThemeConfig { id: "dark".to_string(), name: "Dark Theme".to_string(), description: Some("Dark theme".to_string()), primary_color: "#ffffff".to_string(), secondary_color: "#1a1a1a".to_string(), css_variables: { let mut vars = HashMap::new(); vars.insert("primary".to_string(), "#ffffff".to_string()); vars.insert("secondary".to_string(), "#1a1a1a".to_string()); vars }, is_dark: true, }, ] } #[test] fn test_layout_provider() { let themes = create_test_themes(); let provider = ConfigurableLayoutProvider::new(themes, "default".to_string()); // Test layout retrieval let layout = provider.get_layout("default").unwrap(); assert_eq!(layout.id, "default"); assert!(layout.template.contains("")); // Test theme retrieval let theme = provider.get_theme("default").unwrap(); assert_eq!(theme.id, "default"); assert_eq!(theme.primary_color, "#007acc"); // Test layout application let view = "Test Content".to_string(); let result = provider.apply_layout(&layout, view, &theme, "en").unwrap(); assert!(result.contains("Test Content")); assert!(result.contains("")); } #[test] fn test_default_implementations() { let themes = create_test_themes(); let provider = ConfigurableLayoutProvider::new(themes, "default".to_string()); let default_layout = provider.default_layout().unwrap(); assert_eq!(default_layout.id, "default"); let default_theme = provider.default_theme().unwrap(); assert_eq!(default_theme.id, "default"); } #[test] fn test_layout_error_handling() { let themes = create_test_themes(); let provider = ConfigurableLayoutProvider::new(themes, "default".to_string()); let result = provider.get_layout("nonexistent"); assert!(result.is_err()); match result.unwrap_err() { ComponentError::LayoutError(_) => (), // Expected _ => panic!("Expected LayoutError"), } } #[test] fn test_theme_error_handling() { let themes = create_test_themes(); let provider = ConfigurableLayoutProvider::new(themes, "default".to_string()); let result = provider.get_theme("nonexistent"); assert!(result.is_err()); match result.unwrap_err() { ComponentError::LayoutError(_) => (), // Expected _ => panic!("Expected LayoutError"), } } }