diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index f3bc523ac51e..27bb8dd19681 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1149,15 +1149,17 @@ def _strip_hidden_fields[_T: Mapping[str, Any]]( if (visible_condition := getattr(key, "visible", None)) is not None: conditions[name] = visible_condition + # Match the frontend: hide fields in schema order, dropping each value before + # the next field's condition is evaluated. hidden_names: set[Any] = set() - while newly_hidden := { - name - for name, condition in conditions.items() - if name not in hidden_names and not is_field_visible(condition, eval_data) - }: - hidden_names |= newly_hidden - for name in newly_hidden: - eval_data.pop(name, None) + changed = True + while changed: + changed = False + for name, condition in conditions.items(): + if name not in hidden_names and not is_field_visible(condition, eval_data): + hidden_names.add(name) + eval_data.pop(name, None) + changed = True new_schema: dict[Any, Any] = {} new_input: dict[str, Any] | None = None diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index cf280a31abda..f91687d36b90 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -1624,6 +1624,39 @@ async def test_hidden_field_reads_as_absent_to_other_conditions( assert "extra" in err.value.schema_errors +async def test_hidden_fields_resolved_in_schema_order( + manager: MockFlowManager, +) -> None: + """Test a hidden field drops before a later field's condition is evaluated.""" + schema = vol.Schema( + { + # Always hidden but defaulted; must read as absent to "extra" below it. + data_entry_flow.Optional( + "token", + default="from_default", + visible=False, + ): str, + data_entry_flow.Required( + "extra", + visible={"field": "token", "operator": "not_exists"}, + ): str, + } + ) + + @manager.mock_reg_handler("test") + class TestFlow(data_entry_flow.FlowHandler): + async def async_step_init(self, user_input=None): + if user_input is not None: + return self.async_create_entry(title="Test", data=user_input) + return self.async_show_form(step_id="init", data_schema=schema) + + # "token" is hidden and drops out, so "extra" sees it as absent and stays required + form = await manager.async_init("test") + with pytest.raises(data_entry_flow.InvalidData) as err: + await manager.async_configure(form["flow_id"], {}) + assert "extra" in err.value.schema_errors + + async def test_hidden_field_in_defaulted_section(manager: MockFlowManager) -> None: """Test nested hidden fields are stripped for an omitted defaulted section.""" schema = vol.Schema(