Hide switch_as_x tracked entity (#67949)
* Hide switch_as_x tracked entity * Hide wrapped switch during config flow * Allow setting/getting entity disabled by via WS * Adjust tests * Improve test coverage * Improve testspull/68141/head
parent
8a8d7741d5
commit
314175135f
|
@ -78,6 +78,14 @@ def websocket_get_entity(hass, connection, msg):
|
||||||
er.RegistryEntryDisabler.USER.value,
|
er.RegistryEntryDisabler.USER.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
# We only allow setting hidden_by user via API.
|
||||||
|
vol.Optional("hidden_by"): vol.Any(
|
||||||
|
None,
|
||||||
|
vol.All(
|
||||||
|
vol.Coerce(er.RegistryEntryHider),
|
||||||
|
er.RegistryEntryHider.USER.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@callback
|
@callback
|
||||||
|
@ -96,7 +104,7 @@ def websocket_update_entity(hass, connection, msg):
|
||||||
|
|
||||||
changes = {}
|
changes = {}
|
||||||
|
|
||||||
for key in ("area_id", "device_class", "disabled_by", "icon", "name"):
|
for key in ("area_id", "device_class", "disabled_by", "hidden_by", "icon", "name"):
|
||||||
if key in msg:
|
if key in msg:
|
||||||
changes[key] = msg[key]
|
changes[key] = msg[key]
|
||||||
|
|
||||||
|
@ -113,6 +121,7 @@ def websocket_update_entity(hass, connection, msg):
|
||||||
return
|
return
|
||||||
|
|
||||||
if "disabled_by" in msg and msg["disabled_by"] is None:
|
if "disabled_by" in msg and msg["disabled_by"] is None:
|
||||||
|
# Don't allow enabling an entity of a disabled device
|
||||||
entity = registry.entities[msg["entity_id"]]
|
entity = registry.entities[msg["entity_id"]]
|
||||||
if entity.device_id:
|
if entity.device_id:
|
||||||
device_registry = dr.async_get(hass)
|
device_registry = dr.async_get(hass)
|
||||||
|
@ -135,6 +144,7 @@ def websocket_update_entity(hass, connection, msg):
|
||||||
return
|
return
|
||||||
result = {"entity_entry": _entry_ext_dict(entry)}
|
result = {"entity_entry": _entry_ext_dict(entry)}
|
||||||
if "disabled_by" in changes and changes["disabled_by"] is None:
|
if "disabled_by" in changes and changes["disabled_by"] is None:
|
||||||
|
# Enabling an entity requires a config entry reload, or HA restart
|
||||||
config_entry = hass.config_entries.async_get_entry(entry.config_entry_id)
|
config_entry = hass.config_entries.async_get_entry(entry.config_entry_id)
|
||||||
if config_entry and not config_entry.supports_unload:
|
if config_entry and not config_entry.supports_unload:
|
||||||
result["require_restart"] = True
|
result["require_restart"] = True
|
||||||
|
@ -178,6 +188,7 @@ def _entry_dict(entry):
|
||||||
"disabled_by": entry.disabled_by,
|
"disabled_by": entry.disabled_by,
|
||||||
"entity_category": entry.entity_category,
|
"entity_category": entry.entity_category,
|
||||||
"entity_id": entry.entity_id,
|
"entity_id": entry.entity_id,
|
||||||
|
"hidden_by": entry.hidden_by,
|
||||||
"icon": entry.icon,
|
"icon": entry.icon,
|
||||||
"name": entry.name,
|
"name": entry.name,
|
||||||
"platform": entry.platform,
|
"platform": entry.platform,
|
||||||
|
|
|
@ -101,3 +101,20 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
return await hass.config_entries.async_unload_platforms(
|
return await hass.config_entries.async_unload_platforms(
|
||||||
entry, (entry.options[CONF_TARGET_DOMAIN],)
|
entry, (entry.options[CONF_TARGET_DOMAIN],)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
# Unhide the wrapped entry if registered
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
try:
|
||||||
|
entity_id = er.async_validate_entity_id(registry, entry.options[CONF_ENTITY_ID])
|
||||||
|
except vol.Invalid:
|
||||||
|
# The source entity has been removed from the entity registry
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (entity_entry := registry.async_get(entity_id)):
|
||||||
|
return
|
||||||
|
|
||||||
|
if entity_entry.hidden_by == er.RegistryEntryHider.INTEGRATION:
|
||||||
|
registry.async_update_entity(entity_id, hidden_by=None)
|
||||||
|
|
|
@ -7,7 +7,11 @@ from typing import Any
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_ENTITY_ID, Platform
|
from homeassistant.const import CONF_ENTITY_ID, Platform
|
||||||
from homeassistant.helpers import helper_config_entry_flow, selector
|
from homeassistant.helpers import (
|
||||||
|
entity_registry as er,
|
||||||
|
helper_config_entry_flow,
|
||||||
|
selector,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import CONF_TARGET_DOMAIN, DOMAIN
|
from .const import CONF_TARGET_DOMAIN, DOMAIN
|
||||||
|
|
||||||
|
@ -45,7 +49,15 @@ class SwitchAsXConfigFlowHandler(
|
||||||
config_flow = CONFIG_FLOW
|
config_flow = CONFIG_FLOW
|
||||||
|
|
||||||
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
|
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
|
||||||
"""Return config entry title."""
|
"""Return config entry title and hide the wrapped entity if registered."""
|
||||||
|
# Hide the wrapped entry if registered
|
||||||
|
registry = er.async_get(self.hass)
|
||||||
|
entity_entry = registry.async_get(options[CONF_ENTITY_ID])
|
||||||
|
if entity_entry is not None and not entity_entry.hidden:
|
||||||
|
registry.async_update_entity(
|
||||||
|
options[CONF_ENTITY_ID], hidden_by=er.RegistryEntryHider.INTEGRATION
|
||||||
|
)
|
||||||
|
|
||||||
return helper_config_entry_flow.wrapped_entity_config_entry_title(
|
return helper_config_entry_flow.wrapped_entity_config_entry_title(
|
||||||
self.hass, options[CONF_ENTITY_ID]
|
self.hass, options[CONF_ENTITY_ID]
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,7 +59,7 @@ SAVE_DELAY = 10
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STORAGE_VERSION_MAJOR = 1
|
STORAGE_VERSION_MAJOR = 1
|
||||||
STORAGE_VERSION_MINOR = 5
|
STORAGE_VERSION_MINOR = 6
|
||||||
STORAGE_KEY = "core.entity_registry"
|
STORAGE_KEY = "core.entity_registry"
|
||||||
|
|
||||||
# Attributes relevant to describing entity
|
# Attributes relevant to describing entity
|
||||||
|
@ -85,6 +85,13 @@ class RegistryEntryDisabler(StrEnum):
|
||||||
USER = "user"
|
USER = "user"
|
||||||
|
|
||||||
|
|
||||||
|
class RegistryEntryHider(StrEnum):
|
||||||
|
"""What hid a registry entry."""
|
||||||
|
|
||||||
|
INTEGRATION = "integration"
|
||||||
|
USER = "user"
|
||||||
|
|
||||||
|
|
||||||
# DISABLED_* are deprecated, to be removed in 2022.3
|
# DISABLED_* are deprecated, to be removed in 2022.3
|
||||||
DISABLED_CONFIG_ENTRY = RegistryEntryDisabler.CONFIG_ENTRY.value
|
DISABLED_CONFIG_ENTRY = RegistryEntryDisabler.CONFIG_ENTRY.value
|
||||||
DISABLED_DEVICE = RegistryEntryDisabler.DEVICE.value
|
DISABLED_DEVICE = RegistryEntryDisabler.DEVICE.value
|
||||||
|
@ -120,6 +127,7 @@ class RegistryEntry:
|
||||||
entity_category: EntityCategory | None = attr.ib(
|
entity_category: EntityCategory | None = attr.ib(
|
||||||
default=None, converter=_convert_to_entity_category
|
default=None, converter=_convert_to_entity_category
|
||||||
)
|
)
|
||||||
|
hidden_by: RegistryEntryHider | None = attr.ib(default=None)
|
||||||
icon: str | None = attr.ib(default=None)
|
icon: str | None = attr.ib(default=None)
|
||||||
id: str = attr.ib(factory=uuid_util.random_uuid_hex)
|
id: str = attr.ib(factory=uuid_util.random_uuid_hex)
|
||||||
name: str | None = attr.ib(default=None)
|
name: str | None = attr.ib(default=None)
|
||||||
|
@ -143,6 +151,11 @@ class RegistryEntry:
|
||||||
"""Return if entry is disabled."""
|
"""Return if entry is disabled."""
|
||||||
return self.disabled_by is not None
|
return self.disabled_by is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hidden(self) -> bool:
|
||||||
|
"""Return if entry is hidden."""
|
||||||
|
return self.hidden_by is not None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def write_unavailable_state(self, hass: HomeAssistant) -> None:
|
def write_unavailable_state(self, hass: HomeAssistant) -> None:
|
||||||
"""Write the unavailable state to the state machine."""
|
"""Write the unavailable state to the state machine."""
|
||||||
|
@ -327,8 +340,9 @@ class EntityRegistry:
|
||||||
# To influence entity ID generation
|
# To influence entity ID generation
|
||||||
known_object_ids: Iterable[str] | None = None,
|
known_object_ids: Iterable[str] | None = None,
|
||||||
suggested_object_id: str | None = None,
|
suggested_object_id: str | None = None,
|
||||||
# To disable an entity if it gets created
|
# To disable or hide an entity if it gets created
|
||||||
disabled_by: RegistryEntryDisabler | None = None,
|
disabled_by: RegistryEntryDisabler | None = None,
|
||||||
|
hidden_by: RegistryEntryHider | None = None,
|
||||||
# Data that we want entry to have
|
# Data that we want entry to have
|
||||||
area_id: str | None = None,
|
area_id: str | None = None,
|
||||||
capabilities: Mapping[str, Any] | None = None,
|
capabilities: Mapping[str, Any] | None = None,
|
||||||
|
@ -400,6 +414,7 @@ class EntityRegistry:
|
||||||
disabled_by=disabled_by,
|
disabled_by=disabled_by,
|
||||||
entity_category=_convert_to_entity_category(entity_category),
|
entity_category=_convert_to_entity_category(entity_category),
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
|
hidden_by=hidden_by,
|
||||||
original_device_class=original_device_class,
|
original_device_class=original_device_class,
|
||||||
original_icon=original_icon,
|
original_icon=original_icon,
|
||||||
original_name=original_name,
|
original_name=original_name,
|
||||||
|
@ -505,6 +520,7 @@ class EntityRegistry:
|
||||||
disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED,
|
disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED,
|
||||||
# Type str (ENTITY_CATEG*) is deprecated as of 2021.12, use EntityCategory
|
# Type str (ENTITY_CATEG*) is deprecated as of 2021.12, use EntityCategory
|
||||||
entity_category: EntityCategory | str | None | UndefinedType = UNDEFINED,
|
entity_category: EntityCategory | str | None | UndefinedType = UNDEFINED,
|
||||||
|
hidden_by: RegistryEntryHider | None | UndefinedType = UNDEFINED,
|
||||||
icon: str | None | UndefinedType = UNDEFINED,
|
icon: str | None | UndefinedType = UNDEFINED,
|
||||||
name: str | None | UndefinedType = UNDEFINED,
|
name: str | None | UndefinedType = UNDEFINED,
|
||||||
new_entity_id: str | UndefinedType = UNDEFINED,
|
new_entity_id: str | UndefinedType = UNDEFINED,
|
||||||
|
@ -540,6 +556,7 @@ class EntityRegistry:
|
||||||
("device_id", device_id),
|
("device_id", device_id),
|
||||||
("disabled_by", disabled_by),
|
("disabled_by", disabled_by),
|
||||||
("entity_category", entity_category),
|
("entity_category", entity_category),
|
||||||
|
("hidden_by", hidden_by),
|
||||||
("icon", icon),
|
("icon", icon),
|
||||||
("name", name),
|
("name", name),
|
||||||
("original_device_class", original_device_class),
|
("original_device_class", original_device_class),
|
||||||
|
@ -651,6 +668,7 @@ class EntityRegistry:
|
||||||
entity["entity_category"], raise_report=False
|
entity["entity_category"], raise_report=False
|
||||||
),
|
),
|
||||||
entity_id=entity["entity_id"],
|
entity_id=entity["entity_id"],
|
||||||
|
hidden_by=entity["hidden_by"],
|
||||||
icon=entity["icon"],
|
icon=entity["icon"],
|
||||||
id=entity["id"],
|
id=entity["id"],
|
||||||
name=entity["name"],
|
name=entity["name"],
|
||||||
|
@ -686,6 +704,7 @@ class EntityRegistry:
|
||||||
"disabled_by": entry.disabled_by,
|
"disabled_by": entry.disabled_by,
|
||||||
"entity_category": entry.entity_category,
|
"entity_category": entry.entity_category,
|
||||||
"entity_id": entry.entity_id,
|
"entity_id": entry.entity_id,
|
||||||
|
"hidden_by": entry.hidden_by,
|
||||||
"icon": entry.icon,
|
"icon": entry.icon,
|
||||||
"id": entry.id,
|
"id": entry.id,
|
||||||
"name": entry.name,
|
"name": entry.name,
|
||||||
|
@ -846,6 +865,11 @@ async def _async_migrate(
|
||||||
for entity in data["entities"]:
|
for entity in data["entities"]:
|
||||||
entity["options"] = {}
|
entity["options"] = {}
|
||||||
|
|
||||||
|
if old_major_version == 1 and old_minor_version < 6:
|
||||||
|
# Version 1.6 adds hidden_by
|
||||||
|
for entity in data["entities"]:
|
||||||
|
entity["hidden_by"] = None
|
||||||
|
|
||||||
if old_major_version > 1:
|
if old_major_version > 1:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -4,7 +4,11 @@ import pytest
|
||||||
from homeassistant.components.config import entity_registry
|
from homeassistant.components.config import entity_registry
|
||||||
from homeassistant.const import ATTR_ICON
|
from homeassistant.const import ATTR_ICON
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryDisabler
|
from homeassistant.helpers.device_registry import DeviceEntryDisabler
|
||||||
from homeassistant.helpers.entity_registry import RegistryEntry, RegistryEntryDisabler
|
from homeassistant.helpers.entity_registry import (
|
||||||
|
RegistryEntry,
|
||||||
|
RegistryEntryDisabler,
|
||||||
|
RegistryEntryHider,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
MockConfigEntry,
|
||||||
|
@ -57,6 +61,7 @@ async def test_list_entities(hass, client):
|
||||||
"area_id": None,
|
"area_id": None,
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_id": "test_domain.name",
|
"entity_id": "test_domain.name",
|
||||||
|
"hidden_by": None,
|
||||||
"name": "Hello World",
|
"name": "Hello World",
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"platform": "test_platform",
|
"platform": "test_platform",
|
||||||
|
@ -68,6 +73,7 @@ async def test_list_entities(hass, client):
|
||||||
"area_id": None,
|
"area_id": None,
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_id": "test_domain.no_name",
|
"entity_id": "test_domain.no_name",
|
||||||
|
"hidden_by": None,
|
||||||
"name": None,
|
"name": None,
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"platform": "test_platform",
|
"platform": "test_platform",
|
||||||
|
@ -109,6 +115,7 @@ async def test_get_entity(hass, client):
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_category": None,
|
"entity_category": None,
|
||||||
"entity_id": "test_domain.name",
|
"entity_id": "test_domain.name",
|
||||||
|
"hidden_by": None,
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"name": "Hello World",
|
"name": "Hello World",
|
||||||
"original_device_class": None,
|
"original_device_class": None,
|
||||||
|
@ -136,6 +143,7 @@ async def test_get_entity(hass, client):
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_category": None,
|
"entity_category": None,
|
||||||
"entity_id": "test_domain.no_name",
|
"entity_id": "test_domain.no_name",
|
||||||
|
"hidden_by": None,
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"name": None,
|
"name": None,
|
||||||
"original_device_class": None,
|
"original_device_class": None,
|
||||||
|
@ -170,7 +178,7 @@ async def test_update_entity(hass, client):
|
||||||
assert state.name == "before update"
|
assert state.name == "before update"
|
||||||
assert state.attributes[ATTR_ICON] == "icon:before update"
|
assert state.attributes[ATTR_ICON] == "icon:before update"
|
||||||
|
|
||||||
# UPDATE AREA, DEVICE_CLASS, ICON AND NAME
|
# UPDATE AREA, DEVICE_CLASS, HIDDEN_BY, ICON AND NAME
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
|
@ -178,6 +186,7 @@ async def test_update_entity(hass, client):
|
||||||
"entity_id": "test_domain.world",
|
"entity_id": "test_domain.world",
|
||||||
"area_id": "mock-area-id",
|
"area_id": "mock-area-id",
|
||||||
"device_class": "custom_device_class",
|
"device_class": "custom_device_class",
|
||||||
|
"hidden_by": "user", # We exchange strings over the WS API, not enums
|
||||||
"icon": "icon:after update",
|
"icon": "icon:after update",
|
||||||
"name": "after update",
|
"name": "after update",
|
||||||
}
|
}
|
||||||
|
@ -195,6 +204,7 @@ async def test_update_entity(hass, client):
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_category": None,
|
"entity_category": None,
|
||||||
"entity_id": "test_domain.world",
|
"entity_id": "test_domain.world",
|
||||||
|
"hidden_by": "user", # We exchange strings over the WS API, not enums
|
||||||
"icon": "icon:after update",
|
"icon": "icon:after update",
|
||||||
"name": "after update",
|
"name": "after update",
|
||||||
"original_device_class": None,
|
"original_device_class": None,
|
||||||
|
@ -209,17 +219,33 @@ async def test_update_entity(hass, client):
|
||||||
assert state.name == "after update"
|
assert state.name == "after update"
|
||||||
assert state.attributes[ATTR_ICON] == "icon:after update"
|
assert state.attributes[ATTR_ICON] == "icon:after update"
|
||||||
|
|
||||||
# UPDATE DISABLED_BY TO USER
|
# UPDATE HIDDEN_BY TO ILLEGAL VALUE
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"type": "config/entity_registry/update",
|
"type": "config/entity_registry/update",
|
||||||
"entity_id": "test_domain.world",
|
"entity_id": "test_domain.world",
|
||||||
|
"hidden_by": "ivy",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = await client.receive_json()
|
||||||
|
assert not msg["success"]
|
||||||
|
|
||||||
|
assert registry.entities["test_domain.world"].hidden_by is RegistryEntryHider.USER
|
||||||
|
|
||||||
|
# UPDATE DISABLED_BY TO USER
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"type": "config/entity_registry/update",
|
||||||
|
"entity_id": "test_domain.world",
|
||||||
"disabled_by": RegistryEntryDisabler.USER,
|
"disabled_by": RegistryEntryDisabler.USER,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = await client.receive_json()
|
msg = await client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
|
||||||
assert hass.states.get("test_domain.world") is None
|
assert hass.states.get("test_domain.world") is None
|
||||||
assert (
|
assert (
|
||||||
|
@ -229,7 +255,7 @@ async def test_update_entity(hass, client):
|
||||||
# UPDATE DISABLED_BY TO NONE
|
# UPDATE DISABLED_BY TO NONE
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{
|
{
|
||||||
"id": 8,
|
"id": 9,
|
||||||
"type": "config/entity_registry/update",
|
"type": "config/entity_registry/update",
|
||||||
"entity_id": "test_domain.world",
|
"entity_id": "test_domain.world",
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
|
@ -248,6 +274,7 @@ async def test_update_entity(hass, client):
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_category": None,
|
"entity_category": None,
|
||||||
"entity_id": "test_domain.world",
|
"entity_id": "test_domain.world",
|
||||||
|
"hidden_by": "user", # We exchange strings over the WS API, not enums
|
||||||
"icon": "icon:after update",
|
"icon": "icon:after update",
|
||||||
"name": "after update",
|
"name": "after update",
|
||||||
"original_device_class": None,
|
"original_device_class": None,
|
||||||
|
@ -306,6 +333,7 @@ async def test_update_entity_require_restart(hass, client):
|
||||||
"entity_category": None,
|
"entity_category": None,
|
||||||
"entity_id": "test_domain.world",
|
"entity_id": "test_domain.world",
|
||||||
"icon": None,
|
"icon": None,
|
||||||
|
"hidden_by": None,
|
||||||
"name": None,
|
"name": None,
|
||||||
"original_device_class": None,
|
"original_device_class": None,
|
||||||
"original_icon": None,
|
"original_icon": None,
|
||||||
|
@ -409,6 +437,7 @@ async def test_update_entity_no_changes(hass, client):
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_category": None,
|
"entity_category": None,
|
||||||
"entity_id": "test_domain.world",
|
"entity_id": "test_domain.world",
|
||||||
|
"hidden_by": None,
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"name": "name of entity",
|
"name": "name of entity",
|
||||||
"original_device_class": None,
|
"original_device_class": None,
|
||||||
|
@ -492,6 +521,7 @@ async def test_update_entity_id(hass, client):
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
"entity_category": None,
|
"entity_category": None,
|
||||||
"entity_id": "test_domain.planet",
|
"entity_id": "test_domain.planet",
|
||||||
|
"hidden_by": None,
|
||||||
"icon": None,
|
"icon": None,
|
||||||
"name": None,
|
"name": None,
|
||||||
"original_device_class": None,
|
"original_device_class": None,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""Test the Switch as X config flow."""
|
"""Test the Switch as X config flow."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -8,6 +10,7 @@ from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAI
|
||||||
from homeassistant.const import CONF_ENTITY_ID, Platform
|
from homeassistant.const import CONF_ENTITY_ID, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
|
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("target_domain", (Platform.LIGHT,))
|
@pytest.mark.parametrize("target_domain", (Platform.LIGHT,))
|
||||||
|
@ -49,6 +52,64 @@ async def test_config_flow(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hidden_by_before,hidden_by_after",
|
||||||
|
(
|
||||||
|
(er.RegistryEntryHider.USER.value, er.RegistryEntryHider.USER.value),
|
||||||
|
(None, er.RegistryEntryHider.INTEGRATION.value),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("target_domain", (Platform.LIGHT,))
|
||||||
|
async def test_config_flow_registered_entity(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
target_domain: Platform,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
hidden_by_before: er.RegistryEntryHider | None,
|
||||||
|
hidden_by_after: er.RegistryEntryHider,
|
||||||
|
) -> None:
|
||||||
|
"""Test the config flow hides a registered entity."""
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
switch_entity_entry = registry.async_get_or_create(
|
||||||
|
"switch", "test", "unique", suggested_object_id="ceiling"
|
||||||
|
)
|
||||||
|
assert switch_entity_entry.entity_id == "switch.ceiling"
|
||||||
|
registry.async_update_entity("switch.ceiling", hidden_by=hidden_by_before)
|
||||||
|
|
||||||
|
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"],
|
||||||
|
{
|
||||||
|
CONF_ENTITY_ID: "switch.ceiling",
|
||||||
|
CONF_TARGET_DOMAIN: target_domain,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "ceiling"
|
||||||
|
assert result["data"] == {}
|
||||||
|
assert result["options"] == {
|
||||||
|
CONF_ENTITY_ID: "switch.ceiling",
|
||||||
|
CONF_TARGET_DOMAIN: target_domain,
|
||||||
|
}
|
||||||
|
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 == {
|
||||||
|
CONF_ENTITY_ID: "switch.ceiling",
|
||||||
|
CONF_TARGET_DOMAIN: target_domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_entity_entry = registry.async_get("switch.ceiling")
|
||||||
|
assert switch_entity_entry.hidden_by == hidden_by_after
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("target_domain", (Platform.LIGHT,))
|
@pytest.mark.parametrize("target_domain", (Platform.LIGHT,))
|
||||||
async def test_options(
|
async def test_options(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""Tests for the Switch as X."""
|
"""Tests for the Switch as X."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -356,3 +358,81 @@ async def test_device(hass: HomeAssistant, target_domain: Platform) -> None:
|
||||||
entity_entry = entity_registry.async_get(f"{target_domain}.abc")
|
entity_entry = entity_registry.async_get(f"{target_domain}.abc")
|
||||||
assert entity_entry
|
assert entity_entry
|
||||||
assert entity_entry.device_id == switch_entity_entry.device_id
|
assert entity_entry.device_id == switch_entity_entry.device_id
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("target_domain", (Platform.LIGHT,))
|
||||||
|
async def test_setup_and_remove_config_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
target_domain: Platform,
|
||||||
|
) -> None:
|
||||||
|
"""Test removing a config entry."""
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
switch_as_x_config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={
|
||||||
|
CONF_ENTITY_ID: "switch.test",
|
||||||
|
CONF_TARGET_DOMAIN: target_domain,
|
||||||
|
},
|
||||||
|
title="ABC",
|
||||||
|
)
|
||||||
|
switch_as_x_config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(switch_as_x_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check the state and entity registry entry are present
|
||||||
|
assert hass.states.get(f"{target_domain}.abc") is not None
|
||||||
|
assert registry.async_get(f"{target_domain}.abc") is not None
|
||||||
|
|
||||||
|
# Remove the config entry
|
||||||
|
assert await hass.config_entries.async_remove(switch_as_x_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check the state and entity registry entry are removed
|
||||||
|
assert hass.states.get(f"{target_domain}.my_min_max") is None
|
||||||
|
assert registry.async_get(f"{target_domain}.my_min_max") is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"hidden_by_before,hidden_by_after",
|
||||||
|
(
|
||||||
|
(er.RegistryEntryHider.USER.value, er.RegistryEntryHider.USER.value),
|
||||||
|
(er.RegistryEntryHider.INTEGRATION.value, None),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("target_domain", (Platform.LIGHT,))
|
||||||
|
async def test_reset_hidden_by(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
target_domain: Platform,
|
||||||
|
hidden_by_before: er.RegistryEntryHider | None,
|
||||||
|
hidden_by_after: er.RegistryEntryHider,
|
||||||
|
) -> None:
|
||||||
|
"""Test removing a config entry resets hidden by."""
|
||||||
|
registry = er.async_get(hass)
|
||||||
|
|
||||||
|
switch_entity_entry = registry.async_get_or_create("switch", "test", "unique")
|
||||||
|
registry.async_update_entity(
|
||||||
|
switch_entity_entry.entity_id, hidden_by=hidden_by_before
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the config entry
|
||||||
|
switch_as_x_config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={
|
||||||
|
CONF_ENTITY_ID: switch_entity_entry.id,
|
||||||
|
CONF_TARGET_DOMAIN: target_domain,
|
||||||
|
},
|
||||||
|
title="ABC",
|
||||||
|
)
|
||||||
|
switch_as_x_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
# Remove the config entry
|
||||||
|
assert await hass.config_entries.async_remove(switch_as_x_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check hidden by is reset
|
||||||
|
switch_entity_entry = registry.async_get(switch_entity_entry.entity_id)
|
||||||
|
assert switch_entity_entry.hidden_by == hidden_by_after
|
||||||
|
|
Loading…
Reference in New Issue