Add config flow for cover, fan, light and media_player groups (#67660)

* Add options flow support to HelperConfigFlowHandler

* Add config flow for cover, fan, light and media_player groups

* Update according to review comments

* Update translation strings

* Update translation strings

* Copy schema before adding suggested values
This commit is contained in:
Erik Montnemery 2022-03-07 13:05:04 +01:00 committed by GitHub
parent 6a92081e83
commit a9cc2d2322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 546 additions and 16 deletions

View File

@ -11,6 +11,7 @@ from typing import Any, Union, cast
import voluptuous as vol import voluptuous as vol
from homeassistant import core as ha from homeassistant import core as ha
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_ASSUMED_STATE,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -58,6 +59,14 @@ ATTR_ALL = "all"
SERVICE_SET = "set" SERVICE_SET = "set"
SERVICE_REMOVE = "remove" SERVICE_REMOVE = "remove"
PLATFORMS_CONFIG_ENTRY = [
Platform.BINARY_SENSOR,
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.MEDIA_PLAYER,
]
PLATFORMS = [ PLATFORMS = [
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.COVER, Platform.COVER,
@ -218,6 +227,25 @@ def groups_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]:
return groups return groups
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
hass.config_entries.async_setup_platforms(entry, (entry.options["group_type"],))
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
return True
async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener, called when the config entry options are changed."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(
entry, (entry.options["group_type"],)
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up all groups found defined in the configuration.""" """Set up all groups found defined in the configuration."""
if DOMAIN not in hass.data: if DOMAIN not in hass.data:

View File

@ -0,0 +1,81 @@
"""Config flow for Group integration."""
from __future__ import annotations
from typing import Any, cast
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ENTITIES
from homeassistant.helpers import helper_config_entry_flow, selector
from . import DOMAIN
def basic_group_options_schema(domain: str) -> vol.Schema:
"""Generate options schema."""
return vol.Schema(
{
vol.Required(CONF_ENTITIES): selector.selector(
{"entity": {"domain": domain, "multiple": True}}
),
}
)
def basic_group_config_schema(domain: str) -> vol.Schema:
"""Generate config schema."""
return vol.Schema({vol.Required("name"): selector.selector({"text": {}})}).extend(
basic_group_options_schema(domain).schema
)
STEPS = {
"init": vol.Schema(
{
vol.Required("group_type"): selector.selector(
{
"select": {
"options": [
"cover",
"fan",
"light",
"media_player",
]
}
}
)
}
),
"cover": basic_group_config_schema("cover"),
"fan": basic_group_config_schema("fan"),
"light": basic_group_config_schema("light"),
"media_player": basic_group_config_schema("media_player"),
"cover_options": basic_group_options_schema("cover"),
"fan_options": basic_group_options_schema("fan"),
"light_options": basic_group_options_schema("light"),
"media_player_options": basic_group_options_schema("media_player"),
}
class GroupConfigFlowHandler(
helper_config_entry_flow.HelperConfigFlowHandler, domain=DOMAIN
):
"""Handle a config or options flow for Switch Light."""
steps = STEPS
def async_config_entry_title(self, user_input: dict[str, Any]) -> str:
"""Return config entry title."""
return cast(str, user_input["name"]) if "name" in user_input else ""
@staticmethod
def async_initial_options_step(config_entry: ConfigEntry) -> str:
"""Return initial options step."""
return f"{config_entry.options['group_type']}_options"
def async_next_step(self, step_id: str, user_input: dict[str, Any]) -> str | None:
"""Return next step_id."""
if step_id == "init":
return cast(str, user_input["group_type"])
return None

View File

@ -22,6 +22,7 @@ from homeassistant.components.cover import (
SUPPORT_STOP_TILT, SUPPORT_STOP_TILT,
CoverEntity, CoverEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_ASSUMED_STATE,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -43,7 +44,7 @@ from homeassistant.const import (
STATE_OPENING, STATE_OPENING,
) )
from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.core import Event, HomeAssistant, State, callback
import homeassistant.helpers.config_validation as cv from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
@ -85,6 +86,22 @@ async def async_setup_platform(
) )
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize Light Switch config entry."""
registry = er.async_get(hass)
entity_id = er.async_validate_entity_ids(
registry, config_entry.options[CONF_ENTITIES]
)
async_add_entities(
[CoverGroup(config_entry.entry_id, config_entry.title, entity_id)]
)
class CoverGroup(GroupEntity, CoverEntity): class CoverGroup(GroupEntity, CoverEntity):
"""Representation of a CoverGroup.""" """Representation of a CoverGroup."""

View File

@ -25,6 +25,7 @@ from homeassistant.components.fan import (
SUPPORT_SET_SPEED, SUPPORT_SET_SPEED,
FanEntity, FanEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_ASSUMED_STATE,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -35,7 +36,7 @@ from homeassistant.const import (
STATE_ON, STATE_ON,
) )
from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.core import Event, HomeAssistant, State, callback
import homeassistant.helpers.config_validation as cv from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
@ -78,6 +79,20 @@ async def async_setup_platform(
) )
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize Light Switch config entry."""
registry = er.async_get(hass)
entity_id = er.async_validate_entity_ids(
registry, config_entry.options[CONF_ENTITIES]
)
async_add_entities([FanGroup(config_entry.entry_id, config_entry.title, entity_id)])
class FanGroup(GroupEntity, FanEntity): class FanGroup(GroupEntity, FanEntity):
"""Representation of a FanGroup.""" """Representation of a FanGroup."""

View File

@ -36,6 +36,7 @@ from homeassistant.components.light import (
SUPPORT_WHITE_VALUE, SUPPORT_WHITE_VALUE,
LightEntity, LightEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
@ -48,7 +49,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
) )
from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.core import Event, HomeAssistant, State, callback
import homeassistant.helpers.config_validation as cv from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
@ -92,6 +93,22 @@ async def async_setup_platform(
) )
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize Light Switch config entry."""
registry = er.async_get(hass)
entity_id = er.async_validate_entity_ids(
registry, config_entry.options[CONF_ENTITIES]
)
async_add_entities(
[LightGroup(config_entry.entry_id, config_entry.title, entity_id)]
)
FORWARDED_ATTRIBUTES = frozenset( FORWARDED_ATTRIBUTES = frozenset(
{ {
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,

View File

@ -2,7 +2,10 @@
"domain": "group", "domain": "group",
"name": "Group", "name": "Group",
"documentation": "https://www.home-assistant.io/integrations/group", "documentation": "https://www.home-assistant.io/integrations/group",
"codeowners": ["@home-assistant/core"], "codeowners": [
"@home-assistant/core"
],
"quality_scale": "internal", "quality_scale": "internal",
"iot_class": "calculated" "iot_class": "calculated",
"config_flow": true
} }

View File

@ -32,6 +32,7 @@ from homeassistant.components.media_player import (
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_STEP,
MediaPlayerEntity, MediaPlayerEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
@ -55,7 +56,7 @@ from homeassistant.const import (
STATE_UNKNOWN, STATE_UNKNOWN,
) )
from homeassistant.core import HomeAssistant, State, callback from homeassistant.core import HomeAssistant, State, callback
import homeassistant.helpers.config_validation as cv from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType
@ -96,6 +97,22 @@ async def async_setup_platform(
) )
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize Light Switch config entry."""
registry = er.async_get(hass)
entity_id = er.async_validate_entity_ids(
registry, config_entry.options[CONF_ENTITIES]
)
async_add_entities(
[MediaGroup(config_entry.entry_id, config_entry.title, entity_id)]
)
class MediaGroup(MediaPlayerEntity): class MediaGroup(MediaPlayerEntity):
"""Representation of a Media Group.""" """Representation of a Media Group."""

View File

@ -1,5 +1,67 @@
{ {
"title": "Group", "title": "Group",
"config": {
"step": {
"init": {
"description": "Select group type",
"data": {
"group_type": "Group type"
}
},
"cover": {
"description": "Select group options",
"data": {
"entities": "Group members",
"name": "Group name"
}
},
"cover_options": {
"description": "Select group options",
"data": {
"entities": "Group members"
}
},
"fan": {
"description": "Select group options",
"data": {
"entities": "Group members",
"name": "Group name"
}
},
"fan_options": {
"description": "Select group options",
"data": {
"entities": "Group members"
}
},
"light": {
"description": "Select group options",
"data": {
"entities": "Group members",
"name": "Group name"
}
},
"light_options": {
"description": "Select group options",
"data": {
"entities": "Group members"
}
},
"media_player": {
"description": "Select group options",
"data": {
"entities": "Group members",
"name": "Group name"
}
},
"media_player_options": {
"description": "Select group options",
"data": {
"entities": "Group members"
}
}
}
},
"state": { "state": {
"_": { "_": {
"off": "[%key:common::state::off%]", "off": "[%key:common::state::off%]",

View File

@ -127,6 +127,7 @@ FLOWS = [
"google_travel_time", "google_travel_time",
"gpslogger", "gpslogger",
"gree", "gree",
"group",
"growatt_server", "growatt_server",
"guardian", "guardian",
"habitica", "habitica",

View File

@ -2,13 +2,20 @@
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Awaitable, Callable
import copy
import types
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, FlowResult from homeassistant.data_entry_flow import (
RESULT_TYPE_CREATE_ENTRY,
FlowResult,
UnknownHandler,
)
class HelperCommonFlowHandler: class HelperCommonFlowHandler:
@ -16,19 +23,18 @@ class HelperCommonFlowHandler:
def __init__( def __init__(
self, self,
handler: HelperConfigFlowHandler, handler: HelperConfigFlowHandler | HelperOptionsFlowHandler,
config_entry: config_entries.ConfigEntry | None, config_entry: config_entries.ConfigEntry | None,
) -> None: ) -> None:
"""Initialize a common handler.""" """Initialize a common handler."""
self._handler = handler self._handler = handler
self._options = dict(config_entry.options) if config_entry is not None else {} self._options = dict(config_entry.options) if config_entry is not None else {}
async def async_step(self, _user_input: dict[str, Any] | None = None) -> FlowResult: async def async_step(
self, step_id: str, _user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a step.""" """Handle a step."""
errors = None errors = None
step_id = (
self._handler.cur_step["step_id"] if self._handler.cur_step else "init"
)
if _user_input is not None: if _user_input is not None:
errors = {} errors = {}
try: try:
@ -38,19 +44,28 @@ class HelperCommonFlowHandler:
except vol.Invalid as exc: except vol.Invalid as exc:
errors["base"] = str(exc) errors["base"] = str(exc)
else: else:
self._options.update(user_input)
if ( if (
next_step_id := self._handler.async_next_step(step_id, user_input) next_step_id := self._handler.async_next_step(step_id, user_input)
) is None: ) is None:
title = self._handler.async_config_entry_title(user_input) title = self._handler.async_config_entry_title(user_input)
return self._handler.async_create_entry( return self._handler.async_create_entry(
title=title, data=user_input title=title, data=self._options
) )
return self._handler.async_show_form( return self._handler.async_show_form(
step_id=next_step_id, data_schema=self._handler.steps[next_step_id] step_id=next_step_id, data_schema=self._handler.steps[next_step_id]
) )
schema = dict(self._handler.steps[step_id].schema)
for key in list(schema):
if key in self._options and isinstance(key, vol.Marker):
new_key = copy.copy(key)
new_key.description = {"suggested_value": self._options[key]}
val = schema.pop(key)
schema[new_key] = val
return self._handler.async_show_form( return self._handler.async_show_form(
step_id=step_id, data_schema=self._handler.steps[step_id], errors=errors step_id=step_id, data_schema=vol.Schema(schema), errors=errors
) )
@ -66,6 +81,29 @@ class HelperConfigFlowHandler(config_entries.ConfigFlow):
"""Initialize a subclass, register if possible.""" """Initialize a subclass, register if possible."""
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
@callback
def _async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Get the options flow for this handler."""
if (
cls.async_initial_options_step
is HelperConfigFlowHandler.async_initial_options_step
):
raise UnknownHandler
return HelperOptionsFlowHandler(
config_entry,
cls.steps,
cls.async_config_entry_title,
cls.async_initial_options_step,
cls.async_next_step,
cls.async_validate_input,
)
# Create an async_get_options_flow method
cls.async_get_options_flow = _async_get_options_flow # type: ignore[assignment]
# Create flow step methods for each step defined in the flow schema
for step in cls.steps: for step in cls.steps:
setattr(cls, f"async_step_{step}", cls.async_step) setattr(cls, f"async_step_{step}", cls.async_step)
@ -73,6 +111,17 @@ class HelperConfigFlowHandler(config_entries.ConfigFlow):
"""Initialize config flow.""" """Initialize config flow."""
self._common_handler = HelperCommonFlowHandler(self, None) self._common_handler = HelperCommonFlowHandler(self, None)
@classmethod
@callback
def async_supports_options_flow(
cls, config_entry: config_entries.ConfigEntry
) -> bool:
"""Return options flow support for this handler."""
return (
cls.async_initial_options_step
is not HelperConfigFlowHandler.async_initial_options_step
)
async def async_step_user( async def async_step_user(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> FlowResult: ) -> FlowResult:
@ -81,7 +130,8 @@ class HelperConfigFlowHandler(config_entries.ConfigFlow):
async def async_step(self, user_input: dict[str, Any] | None = None) -> FlowResult: async def async_step(self, user_input: dict[str, Any] | None = None) -> FlowResult:
"""Handle a step.""" """Handle a step."""
result = await self._common_handler.async_step(user_input) step_id = self.cur_step["step_id"] if self.cur_step else "init"
result = await self._common_handler.async_step(step_id, user_input)
if result["type"] == RESULT_TYPE_CREATE_ENTRY: if result["type"] == RESULT_TYPE_CREATE_ENTRY:
result["options"] = result["data"] result["options"] = result["data"]
result["data"] = {} result["data"] = {}
@ -97,9 +147,57 @@ class HelperConfigFlowHandler(config_entries.ConfigFlow):
"""Return next step_id, or None to finish the flow.""" """Return next step_id, or None to finish the flow."""
return None return None
@staticmethod
@callback
def async_initial_options_step(
config_entry: config_entries.ConfigEntry,
) -> str:
"""Return initial step_id of options flow."""
raise UnknownHandler
# pylint: disable-next=no-self-use # pylint: disable-next=no-self-use
async def async_validate_input( async def async_validate_input(
self, hass: HomeAssistant, step_id: str, user_input: dict[str, Any] self, hass: HomeAssistant, step_id: str, user_input: dict[str, Any]
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Validate user input.""" """Validate user input."""
return user_input return user_input
class HelperOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle an options flow for helper integrations."""
def __init__(
self,
config_entry: config_entries.ConfigEntry,
steps: dict[str, vol.Schema],
config_entry_title: Callable[[Any, dict[str, Any]], str],
initial_step: Callable[[config_entries.ConfigEntry], str],
next_step: Callable[[Any, str, dict[str, Any]], str | None],
validate: Callable[
[Any, HomeAssistant, str, dict[str, Any]], Awaitable[dict[str, Any]]
],
) -> None:
"""Initialize options flow."""
self._common_handler = HelperCommonFlowHandler(self, config_entry)
self._config_entry = config_entry
self._initial_step = initial_step(config_entry)
self.async_config_entry_title = types.MethodType(config_entry_title, self)
self.async_next_step = types.MethodType(next_step, self)
self.async_validate_input = types.MethodType(validate, self)
self.steps = steps
for step in self.steps:
if step == "init":
continue
setattr(self, f"async_step_{step}", self.async_step)
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
return await self.async_step(user_input)
async def async_step(self, user_input: dict[str, Any] | None = None) -> FlowResult:
"""Handle a step."""
# pylint: disable-next=unsubscriptable-object # self.cur_step is a dict
step_id = self.cur_step["step_id"] if self.cur_step else self._initial_step
return await self._common_handler.async_step(step_id, user_input)

View File

@ -0,0 +1,191 @@
"""Test the Switch config flow."""
from unittest.mock import patch
import pytest
from homeassistant import config_entries
from homeassistant.components.group import DOMAIN, async_setup_entry
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
@pytest.mark.parametrize(
"group_type,group_state,member_state,member_attributes",
(
("cover", "open", "open", {}),
("fan", "on", "on", {}),
("light", "on", "on", {}),
("media_player", "on", "on", {}),
),
)
async def test_config_flow(
hass: HomeAssistant, group_type, group_state, member_state, member_attributes
) -> None:
"""Test the config flow."""
members = [f"{group_type}.one", f"{group_type}.two"]
for member in members:
hass.states.async_set(member, member_state, member_attributes)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] is None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"group_type": group_type},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == group_type
with patch(
"homeassistant.components.group.async_setup_entry", wraps=async_setup_entry
) as mock_setup_entry:
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"name": "Living Room",
"entities": members,
},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Living Room"
assert result["data"] == {}
assert result["options"] == {
"group_type": group_type,
"entities": members,
"name": "Living Room",
}
assert len(mock_setup_entry.mock_calls) == 1
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
assert config_entry.data == {}
assert config_entry.options == {
"group_type": group_type,
"name": "Living Room",
"entities": members,
}
state = hass.states.get(f"{group_type}.living_room")
assert state.state == group_state
assert state.attributes["entity_id"] == members
def get_suggested(schema, key):
"""Get suggested value for key in voluptuous schema."""
for k in schema.keys():
if k == key:
if k.description is None or "suggested_value" not in k.description:
return None
return k.description["suggested_value"]
# Wanted key absent from schema
raise Exception
@pytest.mark.parametrize(
"group_type,member_state",
(("cover", "open"), ("fan", "on"), ("light", "on"), ("media_player", "on")),
)
async def test_options(hass: HomeAssistant, group_type, member_state) -> None:
"""Test reconfiguring."""
members1 = [f"{group_type}.one", f"{group_type}.two"]
members2 = [f"{group_type}.four", f"{group_type}.five"]
for member in members1:
hass.states.async_set(member, member_state, {})
for member in members2:
hass.states.async_set(member, member_state, {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] is None
assert get_suggested(result["data_schema"].schema, "group_type") is None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"group_type": group_type},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == group_type
assert get_suggested(result["data_schema"].schema, "entities") is None
assert get_suggested(result["data_schema"].schema, "name") is None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"name": "Bed Room",
"entities": members1,
},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
state = hass.states.get(f"{group_type}.bed_room")
assert state.attributes["entity_id"] == members1
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
assert config_entry.data == {}
assert config_entry.options == {
"group_type": group_type,
"entities": members1,
"name": "Bed Room",
}
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == f"{group_type}_options"
assert get_suggested(result["data_schema"].schema, "entities") == members1
assert "name" not in result["data_schema"].schema
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
"entities": members2,
},
)
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
assert result["data"] == {
"group_type": group_type,
"entities": members2,
"name": "Bed Room",
}
assert config_entry.data == {}
assert config_entry.options == {
"group_type": group_type,
"entities": members2,
"name": "Bed Room",
}
assert config_entry.title == "Bed Room"
# Check config entry is reloaded with new options
await hass.async_block_till_done()
state = hass.states.get(f"{group_type}.bed_room")
assert state.attributes["entity_id"] == members2
# Check we don't get suggestions from another entry
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] is None
assert get_suggested(result["data_schema"].schema, "group_type") is None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"group_type": group_type},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == group_type
assert get_suggested(result["data_schema"].schema, "entities") is None
assert get_suggested(result["data_schema"].schema, "name") is None