diff --git a/homeassistant/components/kitchen_sink/__init__.py b/homeassistant/components/kitchen_sink/__init__.py index 88d0c868636..203968aec5c 100644 --- a/homeassistant/components/kitchen_sink/__init__.py +++ b/homeassistant/components/kitchen_sink/__init__.py @@ -70,11 +70,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set the config entry up.""" # Set up demo platforms with config entry await hass.config_entries.async_forward_entry_setups( - config_entry, COMPONENTS_WITH_DEMO_PLATFORM + entry, COMPONENTS_WITH_DEMO_PLATFORM ) # Create issues @@ -85,7 +85,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b await _insert_statistics(hass) # Start a reauth flow - config_entry.async_start_reauth(hass) + entry.async_start_reauth(hass) + + entry.async_on_unload(entry.add_update_listener(_async_update_listener)) # Notify backup listeners hass.async_create_task(_notify_backup_listeners(hass), eager_start=False) @@ -93,6 +95,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b return True +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle update.""" + await hass.config_entries.async_reload(entry.entry_id) + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload config entry.""" # Notify backup listeners diff --git a/homeassistant/components/kitchen_sink/config_flow.py b/homeassistant/components/kitchen_sink/config_flow.py index 019d1dddcad..9cdd44802df 100644 --- a/homeassistant/components/kitchen_sink/config_flow.py +++ b/homeassistant/components/kitchen_sink/config_flow.py @@ -12,7 +12,9 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, + ConfigSubentryFlow, OptionsFlow, + SubentryFlowResult, ) from homeassistant.core import callback @@ -35,6 +37,21 @@ class KitchenSinkConfigFlow(ConfigFlow, domain=DOMAIN): """Get the options flow for this handler.""" return OptionsFlowHandler() + @staticmethod + @callback + def async_get_subentry_flow( + config_entry: ConfigEntry, subentry_type: str + ) -> ConfigSubentryFlow: + """Get the subentry flow for this handler.""" + + return SubentryFlowHandler() + + @classmethod + @callback + def async_supported_subentries(cls, config_entry: ConfigEntry) -> tuple[str, ...]: + """Return subentries supported by this handler.""" + return ("add_entity",) + async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult: """Set the config entry up from yaml.""" return self.async_create_entry(title="Kitchen Sink", data=import_data) @@ -94,3 +111,31 @@ class OptionsFlowHandler(OptionsFlow): } ), ) + + +class SubentryFlowHandler(ConfigSubentryFlow): + """Handle subentry flow.""" + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> SubentryFlowResult: + """Manage the options.""" + return await self.async_step_add_sensor() + + async def async_step_add_sensor( + self, user_input: dict[str, Any] | None = None + ) -> SubentryFlowResult: + """Add a new sensor.""" + if user_input is not None: + title = user_input.pop("name") + return self.async_create_entry(data=user_input, title=title) + + return self.async_show_form( + step_id="add_sensor", + data_schema=vol.Schema( + { + vol.Required("name"): str, + vol.Required("state"): int, + } + ), + ) diff --git a/homeassistant/components/kitchen_sink/sensor.py b/homeassistant/components/kitchen_sink/sensor.py index 95e56c276e4..1ae6accf9de 100644 --- a/homeassistant/components/kitchen_sink/sensor.py +++ b/homeassistant/components/kitchen_sink/sensor.py @@ -11,7 +11,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import UnitOfPower from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import UNDEFINED, StateType, UndefinedType from . import DOMAIN @@ -21,7 +21,8 @@ from .device import async_create_device async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, + # pylint: disable-next=hass-argument-type + async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Everything but the Kitchen Sink config entry.""" async_create_device( @@ -90,6 +91,23 @@ async def async_setup_entry( ] ) + for subentry_id, subentry in config_entry.subentries.items(): + async_add_entities( + [ + DemoSensor( + device_unique_id=subentry_id, + unique_id=subentry_id, + device_name=subentry.title, + entity_name=None, + state=subentry.data["state"], + device_class=None, + state_class=None, + unit_of_measurement=None, + ) + ], + subentry_id=subentry_id, + ) + class DemoSensor(SensorEntity): """Representation of a Demo sensor.""" diff --git a/homeassistant/components/kitchen_sink/strings.json b/homeassistant/components/kitchen_sink/strings.json index c03f909e617..126c2730489 100644 --- a/homeassistant/components/kitchen_sink/strings.json +++ b/homeassistant/components/kitchen_sink/strings.json @@ -9,6 +9,20 @@ } } }, + "config_subentries": { + "add_entity": { + "title": "Add entity", + "step": { + "add_sensor": { + "description": "Configure the new sensor", + "data": { + "name": "[%key:common::config_flow::data::name%]", + "state": "Initial state" + } + } + } + } + }, "options": { "step": { "init": { diff --git a/tests/components/kitchen_sink/snapshots/test_sensor.ambr b/tests/components/kitchen_sink/snapshots/test_sensor.ambr index bbf88c84eca..7b433c40170 100644 --- a/tests/components/kitchen_sink/snapshots/test_sensor.ambr +++ b/tests/components/kitchen_sink/snapshots/test_sensor.ambr @@ -69,3 +69,84 @@ }), }) # --- +# name: test_states_with_subentry + set({ + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Outlet 1 Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.outlet_1_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '50', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Outlet 2 Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.outlet_2_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1500', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Sensor test', + }), + 'context': , + 'entity_id': 'sensor.sensor_test', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Statistics issues Issue 1', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.statistics_issues_issue_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Statistics issues Issue 2', + 'state_class': , + 'unit_of_measurement': 'dogs', + }), + 'context': , + 'entity_id': 'sensor.statistics_issues_issue_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Statistics issues Issue 3', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.statistics_issues_issue_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100', + }), + }) +# --- diff --git a/tests/components/kitchen_sink/test_config_flow.py b/tests/components/kitchen_sink/test_config_flow.py index 5f163d1342e..d78fc08f470 100644 --- a/tests/components/kitchen_sink/test_config_flow.py +++ b/tests/components/kitchen_sink/test_config_flow.py @@ -104,3 +104,36 @@ async def test_options_flow(hass: HomeAssistant) -> None: assert config_entry.options == {"section_1": {"bool": True, "int": 15}} await hass.async_block_till_done() + + +@pytest.mark.usefixtures("no_platforms") +async def test_subentry_flow(hass: HomeAssistant) -> None: + """Test config flow options.""" + config_entry = MockConfigEntry(domain=DOMAIN) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.subentries.async_init( + (config_entry.entry_id, "add_entity") + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "add_sensor" + + result = await hass.config_entries.subentries.async_configure( + result["flow_id"], + user_input={"name": "Sensor 1", "state": 15}, + ) + assert result["type"] is FlowResultType.CREATE_ENTRY + subentry_id = list(config_entry.subentries.keys())[0] + assert config_entry.subentries == { + subentry_id: config_entries.ConfigSubentry( + data={"state": 15}, + subentry_id=subentry_id, + title="Sensor 1", + unique_id=None, + ) + } + + await hass.async_block_till_done() diff --git a/tests/components/kitchen_sink/test_sensor.py b/tests/components/kitchen_sink/test_sensor.py index c4b5f03499e..f4099621efd 100644 --- a/tests/components/kitchen_sink/test_sensor.py +++ b/tests/components/kitchen_sink/test_sensor.py @@ -5,11 +5,14 @@ from unittest.mock import patch import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant import config_entries from homeassistant.components.kitchen_sink import DOMAIN from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import MockConfigEntry + @pytest.fixture async def sensor_only() -> None: @@ -21,14 +24,40 @@ async def sensor_only() -> None: yield -@pytest.fixture(autouse=True) +@pytest.fixture async def setup_comp(hass: HomeAssistant, sensor_only): """Set up demo component.""" assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() +@pytest.mark.usefixtures("setup_comp") async def test_states(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: """Test the expected sensor entities are added.""" states = hass.states.async_all() assert set(states) == snapshot + + +@pytest.mark.usefixtures("sensor_only") +async def test_states_with_subentry( + hass: HomeAssistant, snapshot: SnapshotAssertion +) -> None: + """Test the expected sensor entities are added.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + subentries_data=[ + config_entries.ConfigSubentryData( + data={"state": 15}, + subentry_id="blabla", + title="Sensor test", + unique_id=None, + ) + ], + ) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + states = hass.states.async_all() + assert set(states) == snapshot