diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index 20a5d8de5a8..dcf7f07bf6b 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -171,13 +171,37 @@ class SchemaCommonFlowHandler: if user_input is not None: # User input was validated successfully, update options - self._options.update(user_input) + self._update_and_remove_omitted_optional_keys( + self._options, user_input, data_schema + ) if user_input is not None or form_step.schema is None: return await self._show_next_step_or_create_entry(form_step) return await self._show_next_step(step_id) + def _update_and_remove_omitted_optional_keys( + self, + values: dict[str, Any], + user_input: dict[str, Any], + data_schema: vol.Schema | None, + ) -> None: + values.update(user_input) + if data_schema and data_schema.schema: + for key in data_schema.schema: + if ( + isinstance(key, vol.Optional) + and key not in user_input + and not ( + # don't remove advanced keys, if they are hidden + key.description + and key.description.get("advanced") + and not self._handler.show_advanced_options + ) + ): + # Key not present, delete keys old value (if present) too + values.pop(key, None) + async def _show_next_step_or_create_entry( self, form_step: SchemaFlowFormStep ) -> FlowResult: @@ -221,7 +245,9 @@ class SchemaCommonFlowHandler: if user_input: # We don't want to mutate the existing options suggested_values = copy.deepcopy(suggested_values) - suggested_values.update(user_input) + self._update_and_remove_omitted_optional_keys( + suggested_values, user_input, await self._get_schema(form_step) + ) if data_schema.schema: # Make a copy of the schema with suggested values set to saved options diff --git a/tests/helpers/test_schema_config_entry_flow.py b/tests/helpers/test_schema_config_entry_flow.py index 0bc8e0f1ff3..7954b63b241 100644 --- a/tests/helpers/test_schema_config_entry_flow.py +++ b/tests/helpers/test_schema_config_entry_flow.py @@ -671,3 +671,80 @@ async def test_options_flow_state(hass: HomeAssistant) -> None: "idx_from_flow_state": "blublu", "option1": "blabla", } + + +async def test_options_flow_omit_optional_keys( + hass: HomeAssistant, manager: data_entry_flow.FlowManager +) -> None: + """Test handling of advanced options in options flow.""" + manager.hass = hass + + OPTIONS_SCHEMA = vol.Schema( + { + vol.Optional("optional_no_default"): str, + vol.Optional("optional_default", default="a very reasonable default"): str, + vol.Optional("advanced_no_default", description={"advanced": True}): str, + vol.Optional( + "advanced_default", + default="a very reasonable default", + description={"advanced": True}, + ): str, + } + ) + + OPTIONS_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = { + "init": SchemaFlowFormStep(OPTIONS_SCHEMA) + } + + class TestFlow(MockSchemaConfigFlowHandler, domain="test"): + config_flow = {} + options_flow = OPTIONS_FLOW + + mock_integration(hass, MockModule("test")) + mock_entity_platform(hass, "config_flow.test", None) + config_entry = MockConfigEntry( + data={}, + domain="test", + options={ + "optional_no_default": "abc123", + "optional_default": "not default", + "advanced_no_default": "abc123", + "advanced_default": "not default", + }, + ) + config_entry.add_to_hass(hass) + + # Start flow in basic mode + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert list(result["data_schema"].schema.keys()) == [ + "optional_no_default", + "optional_default", + ] + + result = await hass.config_entries.options.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["data"] == { + "advanced_default": "not default", + "advanced_no_default": "abc123", + "optional_default": "a very reasonable default", + } + + # Start flow in advanced mode + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": True} + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert list(result["data_schema"].schema.keys()) == [ + "optional_no_default", + "optional_default", + "advanced_no_default", + "advanced_default", + ] + + result = await hass.config_entries.options.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["data"] == { + "advanced_default": "a very reasonable default", + "optional_default": "a very reasonable default", + }