Files
leptos-shadcn-ui/docs/remediation/components/form/REMEDIATION_PLAN.md
Peter Hanssens ec459d142c 🔧 Complete refactoring implementation for v0.9.0
- All 9 components fully refactored with modular architecture
- 45+ test modules created and organized
- File size compliance achieved (99% reduction)
- Enterprise-grade code quality implemented
- All compilation issues resolved

Ready for v0.9.0 release publication!
2025-09-22 13:06:22 +10:00

17 KiB

🔧 Form Component Remediation Plan

Priority 1: Critical Form Component - Immediate Action Required

🚨 Critical Issues Summary

The Form component has severe issues that make it unsuitable for production use:

  • ⚠️ 1 test file with basic implementations only
  • ⚠️ ~30% test coverage (estimated)
  • Missing accessibility features (ARIA attributes, focus management)
  • No form validation tests
  • Missing error handling tests
  • No integration tests

🎯 Remediation Strategy

Phase 1: Critical Accessibility Features (Week 1)

Day 1-2: Add ARIA Attributes

Current Problem: Form lacks proper ARIA attributes for accessibility

Target Implementation:

// Enhanced Form with ARIA attributes
#[component]
pub fn Form(
    #[prop(into, optional)] on_submit: Option<Callback<FormData>>,
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] style: Signal<Style>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let form_class = Signal::derive(move || {
        format!("space-y-6 {}", class.get().unwrap_or_default())
    });

    let handle_submit = move |event: SubmitEvent| {
        event.prevent_default();
        if let Some(callback) = &on_submit {
            // Extract form data and call callback
            callback.run(FormData::new());
        }
    };

    view! {
        <form
            class={form_class}
            style={move || style.get().to_string()}
            on:submit=handle_submit
            role="form"
            aria-labelledby="form-title"
            aria-describedby="form-description"
        >
            {children.map(|c| c())}
        </form>
    }
}

Day 3-4: Implement Form Validation System

Current Problem: No comprehensive form validation system

Target Implementation:

// Enhanced FormField with validation
#[component]
pub fn FormField(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] invalid: Signal<bool>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let field_class = Signal::derive(move || {
        let base_class = "space-y-2";
        let invalid_class = if invalid.get() { " data-invalid" } else { "" };
        format!("{}{} {}", base_class, invalid_class, class.get().unwrap_or_default())
    });

    view! {
        <div 
            class={field_class}
            data-invalid=move || invalid.get().to_string()
            aria-invalid=move || invalid.get().to_string()
        >
            {children.map(|c| c())}
        </div>
    }
}

// Enhanced FormLabel with proper association
#[component]
pub fn FormLabel(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] r#for: MaybeProp<String>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let label_class = Signal::derive(move || {
        format!("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 {}", class.get().unwrap_or_default())
    });

    view! {
        <label 
            class={label_class}
            r#for=r#for
        >
            {children.map(|c| c())}
        </label>
    }
}

Day 5: Implement Error Handling and Display

Current Problem: No error handling or display system

Target Implementation:

// Enhanced FormMessage with error display
#[component]
pub fn FormMessage(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(into, optional)] error: MaybeProp<String>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let message_class = Signal::derive(move || {
        format!("text-sm font-medium text-destructive {}", class.get().unwrap_or_default())
    });

    view! {
        <p 
            class={message_class}
            role="alert"
            aria-live="polite"
        >
            {error.get().unwrap_or_default()}
            {children.map(|c| c())}
        </p>
    }
}

// Enhanced FormDescription
#[component]
pub fn FormDescription(
    #[prop(into, optional)] class: MaybeProp<String>,
    #[prop(optional)] children: Option<Children>,
) -> impl IntoView {
    let description_class = Signal::derive(move || {
        format!("text-sm text-muted-foreground {}", class.get().unwrap_or_default())
    });

    view! {
        <p 
            class={description_class}
            id="form-description"
        >
            {children.map(|c| c())}
        </p>
    }
}

Phase 2: Comprehensive Testing (Week 2)

Day 1-2: Form Validation Tests

File: packages/leptos/form/src/tests/validation_tests.rs

#[cfg(test)]
mod validation_tests {
    use super::*;
    use leptos::prelude::*;

    #[test]
    fn test_form_validation_success() {
        let mut validation = FormValidation::new();
        
        // Test valid form
        assert!(validation.is_valid, "Form should be valid initially");
        assert!(validation.errors.is_empty(), "Should have no errors");
    }

    #[test]
    fn test_form_validation_errors() {
        let mut validation = FormValidation::new();
        
        validation.add_error("email", "Email is required");
        validation.add_error("password", "Password is too short");
        
        assert!(!validation.is_valid, "Form should be invalid");
        assert_eq!(validation.errors.len(), 2, "Should have two errors");
        
        let email_error = validation.get_error("email");
        assert_eq!(email_error, Some("Email is required"));
    }

    #[test]
    fn test_form_validation_clear_errors() {
        let mut validation = FormValidation::new();
        
        validation.add_error("email", "Email is required");
        assert!(!validation.is_valid, "Form should be invalid");
        
        validation.errors.clear();
        validation.is_valid = true;
        assert!(validation.is_valid, "Form should be valid after clearing errors");
    }

    #[test]
    fn test_form_validation_multiple_errors_same_field() {
        let mut validation = FormValidation::new();
        
        validation.add_error("email", "Email is required");
        validation.add_error("email", "Email format is invalid");
        
        assert!(!validation.is_valid, "Form should be invalid");
        assert_eq!(validation.errors.len(), 2, "Should have two errors for same field");
    }
}

Day 3-4: Form Submission Tests

File: packages/leptos/form/src/tests/submission_tests.rs

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

    #[test]
    fn test_form_submission() {
        let (submitted, set_submitted) = signal(false);
        let form_data = FormData::new();
        
        let form = Form::new(FormProps {
            on_submit: Some(Callback::new(move |data| {
                set_submitted.set(true);
            })),
            ..Default::default()
        });
        
        // Simulate form submission
        form.props.on_submit.unwrap().run(form_data);
        assert!(submitted.get(), "Form should be submitted");
    }

    #[test]
    fn test_form_submission_with_data() {
        let (submitted_data, set_submitted_data) = signal(FormData::new());
        
        let form = Form::new(FormProps {
            on_submit: Some(Callback::new(move |data| {
                set_submitted_data.set(data);
            })),
            ..Default::default()
        });
        
        let test_data = FormData::new();
        test_data.fields.insert("email".to_string(), "test@example.com".to_string());
        
        form.props.on_submit.unwrap().run(test_data.clone());
        assert_eq!(submitted_data.get().fields.len(), 1, "Should have submitted data");
    }

    #[test]
    fn test_form_submission_validation() {
        let mut validation = FormValidation::new();
        validation.add_error("email", "Email is required");
        
        let form = Form::new(FormProps {
            validation: Some(validation),
            ..Default::default()
        });
        
        // Form should not submit if validation fails
        assert!(!form.props.validation.unwrap().is_valid, "Form should not be valid");
    }
}

Day 5: Accessibility Tests

File: packages/leptos/form/src/tests/accessibility_tests.rs

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

    #[test]
    fn test_form_aria_attributes() {
        let form = Form::new(FormProps {
            ..Default::default()
        });
        
        let rendered = form.render();
        assert!(rendered.contains("role=\"form\""));
        assert!(rendered.contains("aria-labelledby=\"form-title\""));
        assert!(rendered.contains("aria-describedby=\"form-description\""));
    }

    #[test]
    fn test_form_field_aria_attributes() {
        let field = FormField::new(FormFieldProps {
            invalid: Signal::derive(|| true),
            ..Default::default()
        });
        
        let rendered = field.render();
        assert!(rendered.contains("aria-invalid=\"true\""));
        assert!(rendered.contains("data-invalid=\"true\""));
    }

    #[test]
    fn test_form_label_association() {
        let label = FormLabel::new(FormLabelProps {
            r#for: Some("email".to_string()),
            ..Default::default()
        });
        
        let rendered = label.render();
        assert!(rendered.contains("for=\"email\""));
    }

    #[test]
    fn test_form_message_aria_attributes() {
        let message = FormMessage::new(FormMessageProps {
            error: Some("Email is required".to_string()),
            ..Default::default()
        });
        
        let rendered = message.render();
        assert!(rendered.contains("role=\"alert\""));
        assert!(rendered.contains("aria-live=\"polite\""));
    }
}

Phase 3: Integration Testing (Week 3)

Day 1-2: Form with Input Integration Tests

File: packages/leptos/form/src/tests/integration_tests.rs

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

    #[test]
    fn test_form_with_input_fields() {
        let form = Form::new(FormProps {
            children: Some(Children::new()),
            ..Default::default()
        });
        
        let rendered = form.render();
        // Test that form integrates with input fields
        assert!(rendered.contains("form"));
    }

    #[test]
    fn test_form_with_validation() {
        let mut validation = FormValidation::new();
        validation.add_error("email", "Invalid email");
        
        let form = Form::new(FormProps {
            validation: Some(validation),
            ..Default::default()
        });
        
        let rendered = form.render();
        assert!(rendered.contains("form"));
    }

    #[test]
    fn test_form_with_multiple_fields() {
        let form = Form::new(FormProps {
            children: Some(Children::new()),
            ..Default::default()
        });
        
        let rendered = form.render();
        // Test that form can handle multiple fields
        assert!(rendered.contains("form"));
    }

    #[test]
    fn test_form_with_error_display() {
        let mut validation = FormValidation::new();
        validation.add_error("email", "Email is required");
        
        let form = Form::new(FormProps {
            validation: Some(validation),
            ..Default::default()
        });
        
        let rendered = form.render();
        assert!(rendered.contains("form"));
    }
}

Day 3-4: Performance Tests

File: packages/leptos/form/src/tests/performance_tests.rs

#[cfg(test)]
mod performance_tests {
    use super::*;
    use std::time::Instant;

    #[test]
    fn test_form_render_performance() {
        let start = Instant::now();
        let form = Form::new(FormProps::default());
        let _rendered = form.render();
        let duration = start.elapsed();
        
        assert!(duration.as_millis() < 3, "Form render time should be < 3ms");
    }

    #[test]
    fn test_form_validation_performance() {
        let mut validation = FormValidation::new();
        
        let start = Instant::now();
        for i in 0..100 {
            validation.add_error(&format!("field_{}", i), "Error message");
        }
        let duration = start.elapsed();
        
        assert!(duration.as_millis() < 1, "Form validation should be < 1ms");
    }

    #[test]
    fn test_form_submission_performance() {
        let form = Form::new(FormProps::default());
        
        let start = Instant::now();
        let _rendered = form.render();
        let duration = start.elapsed();
        
        assert!(duration.as_millis() < 2, "Form submission should be < 2ms");
    }
}

Day 5: Error Handling Tests

File: packages/leptos/form/src/tests/error_handling_tests.rs

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

    #[test]
    fn test_form_graceful_error_handling() {
        let form = Form::new(FormProps {
            on_submit: None,
            ..Default::default()
        });
        
        let rendered = form.render();
        // Should not panic when on_submit is None
        assert!(rendered.contains("form"));
    }

    #[test]
    fn test_form_validation_error_handling() {
        let mut validation = FormValidation::new();
        validation.add_error("email", "Email is required");
        
        let form = Form::new(FormProps {
            validation: Some(validation),
            ..Default::default()
        });
        
        let rendered = form.render();
        // Should handle validation errors gracefully
        assert!(rendered.contains("form"));
    }

    #[test]
    fn test_form_missing_context_handling() {
        let form = Form::new(FormProps::default());
        let _rendered = form.render();
        
        // Should not panic when context is missing
        assert!(true, "Form should handle missing context gracefully");
    }
}

📋 Implementation Checklist

Week 1: Critical Accessibility Features

  • Day 1: Add ARIA attributes to Form component
  • Day 2: Implement FormField with validation support
  • Day 3: Add FormLabel with proper association
  • Day 4: Implement FormMessage with error display
  • Day 5: Add FormDescription with accessibility

Week 2: Comprehensive Testing

  • Day 1: Implement form validation tests
  • Day 2: Add form submission tests
  • Day 3: Create accessibility tests
  • Day 4: Add integration tests
  • Day 5: Implement performance tests

Week 3: Integration & Error Handling

  • Day 1: Form with input integration tests
  • Day 2: Multiple field integration tests
  • Day 3: Performance benchmarking
  • Day 4: Error handling tests
  • Day 5: Final validation and documentation

Week 4: Validation & Documentation

  • Day 1: Run full test suite
  • Day 2: Performance benchmarking
  • Day 3: Accessibility testing
  • Day 4: Update documentation
  • Day 5: Final validation and sign-off

🎯 Success Metrics

Week 1 Targets

  • ARIA attributes implemented
  • Form validation working
  • Error handling functional
  • Accessibility features added

Week 2 Targets

  • 90% test coverage achieved
  • All test categories implemented
  • Accessibility tests passing
  • Integration tests working

Week 3 Targets

  • Form integration working
  • Performance benchmarks established
  • Error handling implemented
  • Production ready

Week 4 Targets

  • 100% test coverage
  • AAA accessibility compliance
  • Performance targets met
  • Documentation complete

🚨 Risk Mitigation

High-Risk Areas

  1. Form Validation: Complex validation logic implementation
  2. Accessibility: WCAG compliance requirements
  3. Performance: Real-time validation impact
  4. Integration: Form with other components

Mitigation Strategies

  1. Incremental Implementation: Small, testable changes
  2. Comprehensive Testing: Test-driven development
  3. Code Reviews: Peer review for all changes
  4. Rollback Plans: Git branches for each phase

🔧 Tools & Dependencies

Required Dependencies

[dev-dependencies]
wasm-bindgen-test = "0.3"
web-sys = { version = "0.3", features = ["HtmlFormElement", "SubmitEvent"] }

Testing Tools

  • Unit Testing: Rust native testing
  • WASM Testing: wasm-bindgen-test
  • Accessibility Testing: Manual testing + automated checks
  • Performance Testing: Custom benchmarking

🚀 Next Steps

  1. Start immediately with ARIA attributes
  2. Implement form validation as priority
  3. Add error handling for accessibility
  4. Create comprehensive tests for all functionality
  5. Validate production readiness before completion

This remediation plan will transform the Form component from a basic, inaccessible form into a production-ready, fully-accessible form system within 4 weeks.


Remediation plan created: September 20, 2025
Target completion: October 18, 2025