Skip to main content

rosace_forms/
form.rs

1use crate::error::FieldError;
2use crate::field::FormField;
3
4/// A form that aggregates multiple `FormField`s. Since `FormField` clones
5/// share their underlying atoms (D116 Phase 28 Step 8), `Form` itself is
6/// just a `Vec<FormField>` — no separate reactive plumbing needed at the
7/// `Form` level; every method here is `&self` because the fields it holds
8/// are shared handles, not owned data.
9#[derive(Clone)]
10pub struct Form {
11    fields: Vec<FormField>,
12}
13
14impl Form {
15    pub fn new() -> Self { Self { fields: Vec::new() } }
16
17    pub fn field(mut self, f: FormField) -> Self { self.fields.push(f); self }
18
19    pub fn add_field(&mut self, f: FormField) { self.fields.push(f); }
20
21    /// Run validate() on all fields. Returns true only if ALL pass.
22    pub fn validate_all(&self) -> bool {
23        // Collect into Vec to prevent `all()` from short-circuiting — every
24        // field must run its validators so errors are populated for all fields.
25        let results: Vec<bool> = self.fields.iter().map(|f| f.validate()).collect();
26        results.iter().all(|&v| v)
27    }
28
29    /// Collect all errors from all fields.
30    pub fn errors(&self) -> Vec<FieldError> {
31        self.fields.iter().flat_map(|f| f.errors()).collect()
32    }
33
34    /// Get a field by name.
35    pub fn field_named(&self, name: &str) -> Option<&FormField> {
36        self.fields.iter().find(|f| f.name == name)
37    }
38
39    /// True if all fields pass validation (must call validate_all first,
40    /// or rely on a live-validating `.field()` binding — see `TextInput`/
41    /// `TextArea`'s Step 8 seam — to have kept this current).
42    pub fn is_valid(&self) -> bool {
43        self.fields.iter().all(|f| f.is_valid())
44    }
45
46    /// Reset all fields.
47    pub fn reset(&self) {
48        self.fields.iter().for_each(|f| f.reset());
49    }
50
51    /// Number of fields.
52    pub fn len(&self) -> usize { self.fields.len() }
53    pub fn is_empty(&self) -> bool { self.fields.is_empty() }
54
55    /// Validate every field; if all pass, run `on_valid` and return
56    /// `true`. The natural body of a submit button's `on_press` (D116
57    /// Phase 28 Step 8) — `Button::new("Submit").on_press(move || {
58    /// form.submit(|| { ... }); })`. Marks every field touched (via
59    /// `validate_all`'s own `validate()` calls reading the CURRENT
60    /// value — touched status itself comes from `set()`, unaffected
61    /// here), so an untouched-but-invalid field's error becomes visible
62    /// immediately after a failed submit attempt, not just after the
63    /// user happens to edit it.
64    pub fn submit(&self, on_valid: impl FnOnce()) -> bool {
65        let valid = self.validate_all();
66        if valid {
67            on_valid();
68        }
69        valid
70    }
71}
72
73impl Default for Form { fn default() -> Self { Self::new() } }
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::field::FormField;
79    use crate::validator::Required;
80
81    #[test]
82    fn form_new_empty() {
83        let f = Form::new();
84        assert!(f.is_empty());
85        assert_eq!(f.len(), 0);
86    }
87
88    #[test]
89    fn form_add_field() {
90        let mut f = Form::new();
91        f.add_field(FormField::new("email"));
92        assert_eq!(f.len(), 1);
93    }
94
95    #[test]
96    fn form_validate_all_passes() {
97        let form = Form::new()
98            .field(FormField::new("name").rule(Required));
99        form.field_named("name").unwrap().set("alice");
100        assert!(form.validate_all());
101    }
102
103    #[test]
104    fn form_validate_all_fails() {
105        let form = Form::new()
106            .field(FormField::new("name").rule(Required));
107        assert!(!form.validate_all());
108    }
109
110    #[test]
111    fn form_errors_returns_all() {
112        let form = Form::new()
113            .field(FormField::new("name").rule(Required))
114            .field(FormField::new("email").rule(Required));
115        form.validate_all();
116        assert_eq!(form.errors().len(), 2);
117    }
118
119    #[test]
120    fn form_field_named() {
121        let form = Form::new()
122            .field(FormField::new("username"));
123        assert!(form.field_named("username").is_some());
124        assert!(form.field_named("missing").is_none());
125    }
126
127    #[test]
128    fn form_reset_clears_all() {
129        let form = Form::new()
130            .field(FormField::new("name").rule(Required));
131        form.field_named("name").unwrap().set("alice");
132        form.validate_all();
133        form.reset();
134        assert_eq!(form.field_named("name").unwrap().get(), "");
135        assert!(form.errors().is_empty());
136    }
137
138    #[test]
139    fn form_is_valid_after_validate() {
140        let form = Form::new()
141            .field(FormField::new("name").rule(Required));
142        form.field_named("name").unwrap().set("bob");
143        form.validate_all();
144        assert!(form.is_valid());
145    }
146
147    #[test]
148    fn submit_runs_the_callback_only_when_valid() {
149        let form = Form::new().field(FormField::new("name").rule(Required));
150        let mut ran = false;
151        assert!(!form.submit(|| ran = true), "submit must return false when a required field is empty");
152        assert!(!ran, "the callback must not run on a failed submit");
153
154        form.field_named("name").unwrap().set("alice");
155        let mut ran2 = false;
156        assert!(form.submit(|| ran2 = true));
157        assert!(ran2, "the callback must run once validation passes");
158    }
159
160    #[test]
161    fn cloning_a_form_shares_the_same_fields() {
162        let form = Form::new().field(FormField::new("name").rule(Required));
163        let clone = form.clone();
164        clone.field_named("name").unwrap().set("alice");
165        assert!(form.field_named("name").unwrap().is_touched(), "a clone's write must be visible through the original Form");
166    }
167}