Skip to main content

presolve_compiler/
form_reset.rs

1//! I11 immutable declaration-level Form reset programs.
2
3use std::collections::BTreeMap;
4
5use crate::{
6    FieldBindingId, FieldId, FieldResetOperationId, FormEntity, FormFieldBinding, FormFieldEntity,
7    FormId, FormTrackingProducts, ResetPlanId, SerializableValue, SourceProvenance,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FieldResetStep {
12    RestoreInitialValue,
13    WriteBoundControls,
14    ClearDirty,
15    ClearTouched,
16    ClearValidation,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct FieldResetOperation {
21    pub id: FieldResetOperationId,
22    pub field: FieldId,
23    pub initial_value: SerializableValue,
24    pub declaration_order: usize,
25    pub bound_controls: Vec<FieldBindingId>,
26    pub steps: Vec<FieldResetStep>,
27    pub provenance: SourceProvenance,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct FormResetPlan {
32    pub id: ResetPlanId,
33    pub form: FormId,
34    pub operations: Vec<FieldResetOperation>,
35    pub clear_aggregate_validity: bool,
36    pub clear_submission_state: bool,
37    pub schedule_validation: bool,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Default)]
41pub struct ResetProducts {
42    pub plans: BTreeMap<ResetPlanId, FormResetPlan>,
43}
44
45#[must_use]
46/// # Panics
47///
48/// Panics when a valid I3 Field does not have both mandatory I8 tracking
49/// records. That is a violated staged-product invariant.
50pub fn collect_reset_products(
51    forms: &BTreeMap<FormId, FormEntity>,
52    fields: &BTreeMap<FieldId, FormFieldEntity>,
53    bindings: &BTreeMap<FieldBindingId, FormFieldBinding>,
54    tracking: &FormTrackingProducts,
55) -> ResetProducts {
56    let mut plans = BTreeMap::new();
57    for form in forms.values() {
58        let id = ResetPlanId::for_form(&form.id);
59        let mut form_fields = fields
60            .values()
61            .filter(|field| field.owner_form == form.id)
62            .collect::<Vec<_>>();
63        form_fields.sort_by(|left, right| {
64            (left.declaration_order, &left.id).cmp(&(right.declaration_order, &right.id))
65        });
66        let operations = form_fields
67            .into_iter()
68            .map(|field| {
69                let mut bound_controls = bindings
70                    .values()
71                    .filter(|binding| binding.field == field.id)
72                    .collect::<Vec<_>>();
73                bound_controls.sort_by(|left, right| {
74                    (left.authored_order, &left.id).cmp(&(right.authored_order, &right.id))
75                });
76                assert!(
77                    tracking.dirty.tracking(&field.id).is_some()
78                        && tracking.touched.tracking(&field.id).is_some(),
79                    "I11 requires exact I8 tracking records"
80                );
81                FieldResetOperation {
82                    id: FieldResetOperationId::for_plan_and_field(&id, &field.id),
83                    field: field.id.clone(),
84                    initial_value: field.initial_value.clone(),
85                    declaration_order: field.declaration_order,
86                    bound_controls: bound_controls
87                        .into_iter()
88                        .map(|binding| binding.id.clone())
89                        .collect(),
90                    steps: vec![
91                        FieldResetStep::RestoreInitialValue,
92                        FieldResetStep::WriteBoundControls,
93                        FieldResetStep::ClearDirty,
94                        FieldResetStep::ClearTouched,
95                        FieldResetStep::ClearValidation,
96                    ],
97                    provenance: field.provenance.clone(),
98                }
99            })
100            .collect();
101        plans.insert(
102            id.clone(),
103            FormResetPlan {
104                id,
105                form: form.id.clone(),
106                operations,
107                clear_aggregate_validity: true,
108                clear_submission_state: true,
109                schedule_validation: false,
110            },
111        );
112    }
113    ResetProducts { plans }
114}
115
116#[cfg(test)]
117mod tests {
118    #[test]
119    fn creates_ordered_non_revalidating_reset_operations() {
120        let parsed = presolve_parser::parse_file(
121            "src/Form.tsx",
122            r#"@component("x") class X { @form() form!: Form; @field(this.form) first = "a"; @field(this.form) second = false; render() { return <input field={this.first} />; } }"#,
123        );
124        let model = crate::build_application_semantic_model(&parsed);
125        let form = crate::FormId::for_owner(&model.components[0].id, "form");
126        let plan = &model.reset.plans[&crate::ResetPlanId::for_form(&form)];
127        assert!(!plan.schedule_validation);
128        assert_eq!(plan.operations.len(), 2);
129        assert_eq!(plan.operations[0].bound_controls.len(), 1);
130        assert!(plan.operations[1].bound_controls.is_empty());
131    }
132}