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 valuespull/67799/head
parent
6a92081e83
commit
a9cc2d2322
|
@ -11,6 +11,7 @@ from typing import Any, Union, cast
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant import core as ha
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_ENTITY_ID,
|
||||
|
@ -58,6 +59,14 @@ ATTR_ALL = "all"
|
|||
SERVICE_SET = "set"
|
||||
SERVICE_REMOVE = "remove"
|
||||
|
||||
PLATFORMS_CONFIG_ENTRY = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.COVER,
|
||||
Platform.FAN,
|
||||
Platform.LIGHT,
|
||||
Platform.MEDIA_PLAYER,
|
||||
]
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.COVER,
|
||||
|
@ -218,6 +227,25 @@ def groups_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]:
|
|||
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:
|
||||
"""Set up all groups found defined in the configuration."""
|
||||
if DOMAIN not in hass.data:
|
||||
|
|
|
@ -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
|
|
@ -22,6 +22,7 @@ from homeassistant.components.cover import (
|
|||
SUPPORT_STOP_TILT,
|
||||
CoverEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_ENTITY_ID,
|
||||
|
@ -43,7 +44,7 @@ from homeassistant.const import (
|
|||
STATE_OPENING,
|
||||
)
|
||||
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.event import async_track_state_change_event
|
||||
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):
|
||||
"""Representation of a CoverGroup."""
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ from homeassistant.components.fan import (
|
|||
SUPPORT_SET_SPEED,
|
||||
FanEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ASSUMED_STATE,
|
||||
ATTR_ENTITY_ID,
|
||||
|
@ -35,7 +36,7 @@ from homeassistant.const import (
|
|||
STATE_ON,
|
||||
)
|
||||
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.event import async_track_state_change_event
|
||||
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):
|
||||
"""Representation of a FanGroup."""
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ from homeassistant.components.light import (
|
|||
SUPPORT_WHITE_VALUE,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
|
@ -48,7 +49,7 @@ from homeassistant.const import (
|
|||
STATE_UNAVAILABLE,
|
||||
)
|
||||
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.event import async_track_state_change_event
|
||||
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(
|
||||
{
|
||||
ATTR_BRIGHTNESS,
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
"domain": "group",
|
||||
"name": "Group",
|
||||
"documentation": "https://www.home-assistant.io/integrations/group",
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"codeowners": [
|
||||
"@home-assistant/core"
|
||||
],
|
||||
"quality_scale": "internal",
|
||||
"iot_class": "calculated"
|
||||
"iot_class": "calculated",
|
||||
"config_flow": true
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ from homeassistant.components.media_player import (
|
|||
SUPPORT_VOLUME_STEP,
|
||||
MediaPlayerEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
|
@ -55,7 +56,7 @@ from homeassistant.const import (
|
|||
STATE_UNKNOWN,
|
||||
)
|
||||
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.event import async_track_state_change_event
|
||||
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):
|
||||
"""Representation of a Media Group."""
|
||||
|
||||
|
|
|
@ -1,5 +1,67 @@
|
|||
{
|
||||
"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": {
|
||||
"_": {
|
||||
"off": "[%key:common::state::off%]",
|
||||
|
|
|
@ -127,6 +127,7 @@ FLOWS = [
|
|||
"google_travel_time",
|
||||
"gpslogger",
|
||||
"gree",
|
||||
"group",
|
||||
"growatt_server",
|
||||
"guardian",
|
||||
"habitica",
|
||||
|
|
|
@ -2,13 +2,20 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Awaitable, Callable
|
||||
import copy
|
||||
import types
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, FlowResult
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_CREATE_ENTRY,
|
||||
FlowResult,
|
||||
UnknownHandler,
|
||||
)
|
||||
|
||||
|
||||
class HelperCommonFlowHandler:
|
||||
|
@ -16,19 +23,18 @@ class HelperCommonFlowHandler:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
handler: HelperConfigFlowHandler,
|
||||
handler: HelperConfigFlowHandler | HelperOptionsFlowHandler,
|
||||
config_entry: config_entries.ConfigEntry | None,
|
||||
) -> None:
|
||||
"""Initialize a common handler."""
|
||||
self._handler = handler
|
||||
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."""
|
||||
errors = None
|
||||
step_id = (
|
||||
self._handler.cur_step["step_id"] if self._handler.cur_step else "init"
|
||||
)
|
||||
if _user_input is not None:
|
||||
errors = {}
|
||||
try:
|
||||
|
@ -38,19 +44,28 @@ class HelperCommonFlowHandler:
|
|||
except vol.Invalid as exc:
|
||||
errors["base"] = str(exc)
|
||||
else:
|
||||
self._options.update(user_input)
|
||||
if (
|
||||
next_step_id := self._handler.async_next_step(step_id, user_input)
|
||||
) is None:
|
||||
title = self._handler.async_config_entry_title(user_input)
|
||||
return self._handler.async_create_entry(
|
||||
title=title, data=user_input
|
||||
title=title, data=self._options
|
||||
)
|
||||
return self._handler.async_show_form(
|
||||
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(
|
||||
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."""
|
||||
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:
|
||||
setattr(cls, f"async_step_{step}", cls.async_step)
|
||||
|
||||
|
@ -73,6 +111,17 @@ class HelperConfigFlowHandler(config_entries.ConfigFlow):
|
|||
"""Initialize config flow."""
|
||||
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(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
|
@ -81,7 +130,8 @@ class HelperConfigFlowHandler(config_entries.ConfigFlow):
|
|||
|
||||
async def async_step(self, user_input: dict[str, Any] | None = None) -> FlowResult:
|
||||
"""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:
|
||||
result["options"] = result["data"]
|
||||
result["data"] = {}
|
||||
|
@ -97,9 +147,57 @@ class HelperConfigFlowHandler(config_entries.ConfigFlow):
|
|||
"""Return next step_id, or None to finish the flow."""
|
||||
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
|
||||
async def async_validate_input(
|
||||
self, hass: HomeAssistant, step_id: str, user_input: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Validate 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)
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue