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

600 lines
17 KiB
Markdown

# 🔧 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:**
```rust
// 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:**
```rust
// 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:**
```rust
// 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`
```rust
#[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`
```rust
#[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`
```rust
#[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`
```rust
#[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`
```rust
#[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`
```rust
#[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**
```toml
[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*