leptos-motion-core 0.4.0

Core animation engine for Leptos Motion
Documentation
//! Phase 3: Feature Flags Optimization Tests
//! 
//! TDD tests for making gestures, layout, and scroll features optional
//! to achieve 80KB bundle size savings through feature-based compilation.

use std::collections::HashMap;

/// Feature flags analyzer for Phase 3 optimization
pub struct FeatureFlagsPhase3Analyzer {
    /// Current bundle size in bytes
    pub current_size_bytes: u64,
    /// Feature flag configurations
    pub feature_configs: HashMap<String, FeatureConfig>,
    /// Bundle size estimates per feature
    pub feature_sizes: HashMap<String, u64>,
}

/// Configuration for a feature flag
#[derive(Debug, Clone)]
pub struct FeatureConfig {
    /// Feature name
    pub name: String,
    /// Whether the feature is enabled by default
    pub default_enabled: bool,
    /// Dependencies of this feature
    pub dependencies: Vec<String>,
    /// Bundle size impact in bytes
    pub size_impact: u64,
    /// Category of the feature
    pub category: FeatureCategory,
}

/// Categories of features for optimization
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FeatureCategory {
    /// Core animation features (always required)
    Core,
    /// Gesture recognition features
    Gestures,
    /// Layout animation features
    Layout,
    /// Scroll-based animation features
    Scroll,
    /// Performance monitoring features
    Performance,
    /// Developer tools features
    Developer,
}

impl FeatureFlagsPhase3Analyzer {
    /// Create a new analyzer
    pub fn new() -> Self {
        Self {
            current_size_bytes: 378_000, // Current WASM size
            feature_configs: HashMap::new(),
            feature_sizes: HashMap::new(),
        }
    }

    /// Analyze feature flags for Phase 3 optimization
    pub fn analyze_feature_flags(&mut self) {
        // Initialize feature configurations
        self.initialize_feature_configs();
        
        // Calculate feature sizes
        self.calculate_feature_sizes();
        
        // Analyze optimization potential
        self.analyze_optimization_potential();
    }

    /// Initialize feature configurations
    fn initialize_feature_configs(&mut self) {
        // Core features (always required)
        self.feature_configs.insert("core-animations".to_string(), FeatureConfig {
            name: "core-animations".to_string(),
            default_enabled: true,
            dependencies: vec![],
            size_impact: 0, // Core features don't add size
            category: FeatureCategory::Core,
        });

        // Gesture features (optional for 80KB savings)
        self.feature_configs.insert("gesture-support".to_string(), FeatureConfig {
            name: "gesture-support".to_string(),
            default_enabled: true,
            dependencies: vec!["core-animations".to_string()],
            size_impact: 35_000, // 35KB for gesture system
            category: FeatureCategory::Gestures,
        });

        self.feature_configs.insert("drag-gestures".to_string(), FeatureConfig {
            name: "drag-gestures".to_string(),
            default_enabled: true,
            dependencies: vec!["gesture-support".to_string()],
            size_impact: 15_000, // 15KB for drag gestures
            category: FeatureCategory::Gestures,
        });

        self.feature_configs.insert("multi-touch".to_string(), FeatureConfig {
            name: "multi-touch".to_string(),
            default_enabled: true,
            dependencies: vec!["gesture-support".to_string()],
            size_impact: 20_000, // 20KB for multi-touch
            category: FeatureCategory::Gestures,
        });

        // Layout features (optional for 80KB savings)
        self.feature_configs.insert("layout-animations".to_string(), FeatureConfig {
            name: "layout-animations".to_string(),
            default_enabled: true,
            dependencies: vec!["core-animations".to_string()],
            size_impact: 40_000, // 40KB for layout animations
            category: FeatureCategory::Layout,
        });

        self.feature_configs.insert("flip-animations".to_string(), FeatureConfig {
            name: "flip-animations".to_string(),
            default_enabled: true,
            dependencies: vec!["layout-animations".to_string()],
            size_impact: 25_000, // 25KB for FLIP animations
            category: FeatureCategory::Layout,
        });

        // Scroll features (optional for 80KB savings)
        self.feature_configs.insert("scroll-animations".to_string(), FeatureConfig {
            name: "scroll-animations".to_string(),
            default_enabled: true,
            dependencies: vec!["core-animations".to_string()],
            size_impact: 30_000, // 30KB for scroll animations
            category: FeatureCategory::Scroll,
        });

        self.feature_configs.insert("parallax-effects".to_string(), FeatureConfig {
            name: "parallax-effects".to_string(),
            default_enabled: true,
            dependencies: vec!["scroll-animations".to_string()],
            size_impact: 20_000, // 20KB for parallax
            category: FeatureCategory::Scroll,
        });
    }

    /// Calculate feature sizes
    fn calculate_feature_sizes(&mut self) {
        for (name, config) in &self.feature_configs {
            self.feature_sizes.insert(name.clone(), config.size_impact);
        }
    }

    /// Analyze optimization potential
    fn analyze_optimization_potential(&self) {
        // Calculate potential savings by making optional features conditional
        let optional_features = self.feature_configs.values()
            .filter(|config| config.category != FeatureCategory::Core)
            .collect::<Vec<_>>();

        let total_optional_size: u64 = optional_features.iter()
            .map(|config| config.size_impact)
            .sum();

        println!("Phase 3 Feature Flags Analysis:");
        println!("- Current bundle size: {}KB", self.current_size_bytes / 1000);
        println!("- Optional features size: {}KB", total_optional_size / 1000);
        println!("- Potential savings: {}KB", total_optional_size / 1000);
        println!("- Target savings: 80KB");
    }

    /// Get minimal bundle size (core features only)
    pub fn get_minimal_bundle_size(&self) -> u64 {
        let core_size = self.feature_configs.values()
            .filter(|config| config.category == FeatureCategory::Core)
            .map(|config| config.size_impact)
            .sum::<u64>();

        // Base size + core features
        50_000 + core_size // 50KB base + core features
    }

    /// Get bundle size with specific features enabled
    pub fn get_bundle_size_with_features(&self, enabled_features: &[String]) -> u64 {
        let base_size = 50_000; // 50KB base
        let feature_size: u64 = enabled_features.iter()
            .filter_map(|feature| self.feature_sizes.get(feature))
            .sum();

        base_size + feature_size
    }

    /// Get optimization potential for Phase 3
    pub fn get_phase3_optimization_potential(&self) -> u64 {
        // Calculate savings from making gestures, layout, scroll optional
        let gestures_size: u64 = self.feature_configs.values()
            .filter(|config| config.category == FeatureCategory::Gestures)
            .map(|config| config.size_impact)
            .sum();

        let layout_size: u64 = self.feature_configs.values()
            .filter(|config| config.category == FeatureCategory::Layout)
            .map(|config| config.size_impact)
            .sum();

        let scroll_size: u64 = self.feature_configs.values()
            .filter(|config| config.category == FeatureCategory::Scroll)
            .map(|config| config.size_impact)
            .sum();

        gestures_size + layout_size + scroll_size
    }

    /// Check if target is achievable with feature flags
    pub fn is_target_achievable_with_feature_flags(&self) -> bool {
        let potential_savings = self.get_phase3_optimization_potential();
        potential_savings >= 80_000 // 80KB target
    }

    /// Get feature flag effectiveness percentage
    pub fn get_feature_flag_effectiveness(&self) -> f64 {
        let potential_savings = self.get_phase3_optimization_potential();
        let target_savings = 80_000.0;
        
        if target_savings > 0.0 {
            (potential_savings as f64 / target_savings * 100.0).min(100.0)
        } else {
            0.0
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Test feature flag analyzer initialization
    #[test]
    fn test_feature_flag_analyzer_initialization() {
        let analyzer = FeatureFlagsPhase3Analyzer::new();
        assert_eq!(analyzer.current_size_bytes, 378_000);
        assert!(analyzer.feature_configs.is_empty());
        assert!(analyzer.feature_sizes.is_empty());
    }

    /// Test feature configuration setup
    #[test]
    fn test_feature_configuration_setup() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        // Test that core features are configured
        assert!(analyzer.feature_configs.contains_key("core-animations"));
        
        // Test that optional features are configured
        assert!(analyzer.feature_configs.contains_key("gesture-support"));
        assert!(analyzer.feature_configs.contains_key("layout-animations"));
        assert!(analyzer.feature_configs.contains_key("scroll-animations"));

        // Test feature categories
        let core_config = analyzer.feature_configs.get("core-animations").unwrap();
        assert_eq!(core_config.category, FeatureCategory::Core);
        assert!(core_config.default_enabled);

        let gesture_config = analyzer.feature_configs.get("gesture-support").unwrap();
        assert_eq!(gesture_config.category, FeatureCategory::Gestures);
        assert!(gesture_config.default_enabled);
    }

    /// Test bundle size calculations
    #[test]
    fn test_bundle_size_calculations() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        // Test minimal bundle size (core only)
        let minimal_size = analyzer.get_minimal_bundle_size();
        assert!(minimal_size > 0);
        assert!(minimal_size < 100_000); // Should be under 100KB

        // Test bundle size with specific features
        let core_only_size = analyzer.get_bundle_size_with_features(&["core-animations".to_string()]);
        assert!(core_only_size >= minimal_size);

        let with_gestures_size = analyzer.get_bundle_size_with_features(&[
            "core-animations".to_string(),
            "gesture-support".to_string(),
        ]);
        assert!(with_gestures_size > core_only_size);
    }

    /// Test optimization potential calculation
    #[test]
    fn test_optimization_potential_calculation() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        let potential_savings = analyzer.get_phase3_optimization_potential();
        assert!(potential_savings > 0);
        
        // Should be able to save at least 80KB through feature flags
        assert!(potential_savings >= 80_000);
    }

    /// Test target achievability with feature flags
    #[test]
    fn test_target_achievable_with_feature_flags() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        // Should be able to achieve 80KB savings through feature flags
        assert!(analyzer.is_target_achievable_with_feature_flags());
    }

    /// Test feature flag effectiveness
    #[test]
    fn test_feature_flag_effectiveness() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        let effectiveness = analyzer.get_feature_flag_effectiveness();
        assert!(effectiveness > 0.0);
        assert!(effectiveness <= 100.0);
        
        // Should achieve at least 100% of the 80KB target
        assert!(effectiveness >= 100.0);
    }

    /// Test feature dependency resolution
    #[test]
    fn test_feature_dependency_resolution() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        // Test that features have proper dependencies
        let drag_config = analyzer.feature_configs.get("drag-gestures").unwrap();
        assert!(drag_config.dependencies.contains(&"gesture-support".to_string()));

        let flip_config = analyzer.feature_configs.get("flip-animations").unwrap();
        assert!(flip_config.dependencies.contains(&"layout-animations".to_string()));
    }

    /// Test feature categorization
    #[test]
    fn test_feature_categorization() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        // Test gesture features
        let gesture_config = analyzer.feature_configs.get("gesture-support").unwrap();
        assert_eq!(gesture_config.category, FeatureCategory::Gestures);

        let drag_config = analyzer.feature_configs.get("drag-gestures").unwrap();
        assert_eq!(drag_config.category, FeatureCategory::Gestures);

        // Test layout features
        let layout_config = analyzer.feature_configs.get("layout-animations").unwrap();
        assert_eq!(layout_config.category, FeatureCategory::Layout);

        // Test scroll features
        let scroll_config = analyzer.feature_configs.get("scroll-animations").unwrap();
        assert_eq!(scroll_config.category, FeatureCategory::Scroll);
    }

    /// Test conditional compilation simulation
    #[test]
    fn test_conditional_compilation_simulation() {
        let mut analyzer = FeatureFlagsPhase3Analyzer::new();
        analyzer.analyze_feature_flags();

        // Simulate minimal build (core only)
        let minimal_features = vec!["core-animations".to_string()];
        let minimal_size = analyzer.get_bundle_size_with_features(&minimal_features);
        
        // Simulate standard build (core + basic features)
        let standard_features = vec![
            "core-animations".to_string(),
            "gesture-support".to_string(),
        ];
        let standard_size = analyzer.get_bundle_size_with_features(&standard_features);
        
        // Simulate full build (all features)
        let full_features = vec![
            "core-animations".to_string(),
            "gesture-support".to_string(),
            "drag-gestures".to_string(),
            "multi-touch".to_string(),
            "layout-animations".to_string(),
            "flip-animations".to_string(),
            "scroll-animations".to_string(),
            "parallax-effects".to_string(),
        ];
        let full_size = analyzer.get_bundle_size_with_features(&full_features);

        // Verify size progression
        assert!(minimal_size < standard_size);
        assert!(standard_size < full_size);
        
        // Verify significant size difference
        let size_difference = full_size - minimal_size;
        assert!(size_difference >= 80_000); // At least 80KB difference
    }
}