diff --git a/src/openforms/submissions/form_logic.py b/src/openforms/submissions/form_logic.py index 3ebb7053a0..9bd0d4611a 100644 --- a/src/openforms/submissions/form_logic.py +++ b/src/openforms/submissions/form_logic.py @@ -12,14 +12,9 @@ from openforms.formio.utils import get_component_empty_value from openforms.typing import DataMapping -from .logic.actions import PropertyAction +from .logic.actions import ActionOperation from .logic.datastructures import DataContainer -from .logic.rules import ( - EvaluatedRule, - get_current_step, - get_rules_to_evaluate, - iter_evaluate_rules, -) +from .logic.rules import EvaluatedRule, get_rules_to_evaluate, iter_evaluate_rules from .models.submission_step import DirtyData if TYPE_CHECKING: @@ -205,7 +200,6 @@ def check_submission_logic( if not submission_state.form_steps: return - step = get_current_step(submission) rules = get_rules_to_evaluate(submission) # load the data state and all variables @@ -214,15 +208,19 @@ def check_submission_logic( submission_variables_state.set_values(unsaved_data) data_container = DataContainer(state=submission_variables_state) - mutation_operations = [] + mutation_operations: list[ActionOperation] = [] for operation in iter_evaluate_rules(rules, data_container, submission): - # component mutations can be skipped as we're not in the context of a single - # form step. - if isinstance(operation, PropertyAction): - continue mutation_operations.append(operation) + # we loop over all steps because we have validations that ensure unique component + # keys across multiple steps for the whole form. + # + # We need to apply all component mutations at this stage too, because for validation + # reasons, a serializer is built from them. for mutation in mutation_operations: - mutation.apply(step, {}) + for step in submission_state.submission_steps: + assert step.form_step + configuration = step.form_step.form_definition.configuration_wrapper + mutation.apply(step, configuration) submission._form_logic_evaluated = True diff --git a/src/openforms/tests/e2e/test_input_validation_regressions.py b/src/openforms/tests/e2e/test_input_validation_regressions.py index 71a3f39f03..6c0ee7066d 100644 --- a/src/openforms/tests/e2e/test_input_validation_regressions.py +++ b/src/openforms/tests/e2e/test_input_validation_regressions.py @@ -5,7 +5,11 @@ from furl import furl from playwright.async_api import expect -from openforms.forms.tests.factories import FormFactory, FormStepFactory +from openforms.forms.tests.factories import ( + FormFactory, + FormLogicFactory, + FormStepFactory, +) from .base import E2ETestCase, browser_page @@ -226,3 +230,84 @@ def setUpTestData(): await expect( page.get_by_text("Een moment geduld", exact=False) ).to_be_visible() + + @tag("dh-671") + async def test_component_not_required_via_logic(self): + @sync_to_async + def setUpTestData(): + form = FormFactory.create( + slug="validation", + generate_minimal_setup=True, + registration_backend=None, + translation_enabled=False, # force Dutch + ask_privacy_consent=False, + ask_statement_of_truth=False, + formstep__next_text="Volgende", + formstep__form_definition__configuration={ + "components": [ + { + "type": "textfield", + "key": "textfield", + "label": "Visible text field", + "validate": {"required": True}, + }, + ] + }, + ) + # add a second step + FormStepFactory.create( + form=form, + next_text="Volgende", + is_applicable=True, # should not be validated + form_definition__configuration={ + "components": [ + { + "type": "textfield", + "key": "textfield2", + "label": "Textfield 2", + "validate": {"required": False}, + }, + ] + }, + ) + FormLogicFactory.create( + form=form, + json_logic_trigger=True, + actions=[ + { + "component": "textfield", + "formStepUuid": None, + "action": { + "type": "property", + "property": {"value": "validate.required", "type": "bool"}, + "state": False, + }, + } + ], + ) + return form + + form = await setUpTestData() + form_url = str( + furl(self.live_server_url) + / reverse("forms:form-detail", kwargs={"slug": form.slug}) + ) + + async with browser_page() as page: + await page.goto(form_url) + # Start the form + await page.get_by_role("button", name="Formulier starten").click() + + # Confirm first step is visible + await expect(page.get_by_label("Visible text field")).to_be_visible() + await page.get_by_role("button", name="Volgende").click() + + # Handle second step + await expect(page.get_by_label("Textfield 2")).to_be_visible() + await page.get_by_role("button", name="Volgende").click() + + # Confirm and finish the form + await page.get_by_role("button", name="Verzenden").click() + await expect( + page.get_by_text("Een moment geduld", exact=False) + ).to_be_visible()