diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index a79db949ab0..79a0e71f969 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -330,13 +330,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_create_entry(title="", data=self.hk_options) all_supported_devices = await _async_get_supported_devices(self.hass) + # Strip out devices that no longer exist to prevent error in the UI + devices = [ + device_id + for device_id in self.hk_options.get(CONF_DEVICES, []) + if device_id in all_supported_devices + ] return self.async_show_form( step_id="advanced", data_schema=vol.Schema( { - vol.Optional( - CONF_DEVICES, default=self.hk_options.get(CONF_DEVICES, []) - ): cv.multi_select(all_supported_devices) + vol.Optional(CONF_DEVICES, default=devices): cv.multi_select( + all_supported_devices + ) } ), ) @@ -445,13 +451,16 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) data_schema = {} - entities = entity_filter.get(CONF_INCLUDE_ENTITIES, []) - if self.hk_options[CONF_HOMEKIT_MODE] == HOMEKIT_MODE_ACCESSORY: - entity_schema = vol.In - else: - if entities: - include_exclude_mode = MODE_INCLUDE - else: + entity_schema = vol.In + # Strip out entities that no longer exist to prevent error in the UI + entities = [ + entity_id + for entity_id in entity_filter.get(CONF_INCLUDE_ENTITIES, []) + if entity_id in all_supported_entities + ] + if self.hk_options[CONF_HOMEKIT_MODE] != HOMEKIT_MODE_ACCESSORY: + include_exclude_mode = MODE_INCLUDE + if not entities: include_exclude_mode = MODE_EXCLUDE entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, []) data_schema[ diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index e5d395a1f29..f1564f9e3ae 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -365,7 +365,24 @@ async def test_options_flow_devices( mock_hap, hass, demo_cleanup, device_reg, entity_reg, mock_get_source_ip ): """Test devices can be bridged.""" - config_entry = _mock_config_entry_with_options_populated() + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + options={ + "devices": ["notexist"], + "filter": { + "include_domains": [ + "fan", + "humidifier", + "vacuum", + "media_player", + "climate", + "alarm_control_panel", + ], + "exclude_entities": ["climate.front_gate"], + }, + }, + ) config_entry.add_to_hass(hass) demo_config_entry = MockConfigEntry(domain="domain") @@ -491,6 +508,60 @@ async def test_options_flow_devices_preserved_when_advanced_off( } +async def test_options_flow_with_non_existant_entity(hass, mock_get_source_ip): + """Test config flow options in include mode.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + options={ + "filter": { + "include_entities": ["climate.not_exist", "climate.front_gate"], + }, + }, + ) + config_entry.add_to_hass(hass) + hass.states.async_set("climate.front_gate", "off") + hass.states.async_set("climate.new", "off") + + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": False} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={"domains": ["fan", "vacuum", "climate"]}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "include_exclude" + + entities = result["data_schema"]({})["entities"] + assert "climate.not_exist" not in entities + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "entities": ["climate.new", "climate.front_gate"], + "include_exclude_mode": "include", + }, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == { + "mode": "bridge", + "filter": { + "exclude_domains": [], + "exclude_entities": [], + "include_domains": ["fan", "vacuum"], + "include_entities": ["climate.new", "climate.front_gate"], + }, + } + + async def test_options_flow_include_mode_basic(hass, mock_get_source_ip): """Test config flow options in include mode."""