Add invert option to switch_as_x (#107535)

* Add invert option to switch_as_x

* Store invert flag in entity options

* Add options flow

* Update strings

* Add tests

* Address review comment

* Update homeassistant/components/switch_as_x/strings.json

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Address review comments

* Inline get_suggested which was only used once in tests

* Address review comments

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
Erik Montnemery 2024-01-24 16:35:08 +01:00 committed by GitHub
parent c3de193e2e
commit 4b2b4ae36b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 939 additions and 54 deletions

View file

@ -13,7 +13,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.event import async_track_entity_registry_updated_event
from homeassistant.helpers.typing import EventType
from .const import CONF_TARGET_DOMAIN
from .const import CONF_INVERT, CONF_TARGET_DOMAIN
from .light import LightSwitch
__all__ = ["LightSwitch"]
@ -91,6 +91,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass, entity_id, async_registry_updated
)
)
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
device_id = async_add_to_device(hass, entry, entity_id)
@ -100,6 +101,36 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug(
"Migrating from version %s.%s", config_entry.version, config_entry.minor_version
)
if config_entry.version > 1:
# This means the user has downgraded from a future version
return False
if config_entry.version == 1:
options = {**config_entry.options}
if config_entry.minor_version < 2:
options.setdefault(CONF_INVERT, False)
config_entry.minor_version = 2
hass.config_entries.async_update_entry(config_entry, options=options)
_LOGGER.debug(
"Migration to version %s.%s successful",
config_entry.version,
config_entry.minor_version,
)
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(

View file

@ -14,7 +14,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
wrapped_entity_config_entry_title,
)
from .const import CONF_TARGET_DOMAIN, DOMAIN
from .const import CONF_INVERT, CONF_TARGET_DOMAIN, DOMAIN
TARGET_DOMAIN_OPTIONS = [
selector.SelectOptionDict(value=Platform.COVER, label="Cover"),
@ -32,6 +32,7 @@ CONFIG_FLOW = {
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
selector.EntitySelectorConfig(domain=Platform.SWITCH),
),
vol.Optional(CONF_INVERT, default=False): selector.BooleanSelector(),
vol.Required(CONF_TARGET_DOMAIN): selector.SelectSelector(
selector.SelectSelectorConfig(options=TARGET_DOMAIN_OPTIONS),
),
@ -40,11 +41,21 @@ CONFIG_FLOW = {
)
}
OPTIONS_FLOW = {
"init": SchemaFlowFormStep(
vol.Schema({vol.Required(CONF_INVERT): selector.BooleanSelector()})
),
}
class SwitchAsXConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config flow for Switch as X."""
config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW
VERSION = 1
MINOR_VERSION = 2
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
"""Return config entry title and hide the wrapped entity if registered."""

View file

@ -4,4 +4,5 @@ from typing import Final
DOMAIN: Final = "switch_as_x"
CONF_INVERT: Final = "invert"
CONF_TARGET_DOMAIN: Final = "target_domain"

View file

@ -23,7 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import EventStateChangedData
from homeassistant.helpers.typing import EventType
from .entity import BaseEntity
from .const import CONF_INVERT
from .entity import BaseInvertableEntity
async def async_setup_entry(
@ -43,6 +44,7 @@ async def async_setup_entry(
hass,
config_entry.title,
COVER_DOMAIN,
config_entry.options[CONF_INVERT],
entity_id,
config_entry.entry_id,
)
@ -50,7 +52,7 @@ async def async_setup_entry(
)
class CoverSwitch(BaseEntity, CoverEntity):
class CoverSwitch(BaseInvertableEntity, CoverEntity):
"""Represents a Switch as a Cover."""
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
@ -59,7 +61,7 @@ class CoverSwitch(BaseEntity, CoverEntity):
"""Open the cover."""
await self.hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
SERVICE_TURN_OFF if self._invert_state else SERVICE_TURN_ON,
{ATTR_ENTITY_ID: self._switch_entity_id},
blocking=True,
context=self._context,
@ -69,7 +71,7 @@ class CoverSwitch(BaseEntity, CoverEntity):
"""Close cover."""
await self.hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON if self._invert_state else SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: self._switch_entity_id},
blocking=True,
context=self._context,
@ -87,4 +89,7 @@ class CoverSwitch(BaseEntity, CoverEntity):
):
return
if self._invert_state:
self._attr_is_closed = state.state == STATE_ON
else:
self._attr_is_closed = state.state != STATE_ON

View file

@ -106,7 +106,7 @@ class BaseEntity(Entity):
registry.async_update_entity_options(
self.entity_id,
SWITCH_AS_X_DOMAIN,
{"entity_id": self._switch_entity_id},
self.async_generate_entity_options(),
)
if not self._is_new_entity or not (
@ -141,6 +141,11 @@ class BaseEntity(Entity):
copy_custom_name(wrapped_switch)
copy_expose_settings()
@callback
def async_generate_entity_options(self) -> dict[str, Any]:
"""Generate entity options."""
return {"entity_id": self._switch_entity_id, "invert": False}
class BaseToggleEntity(BaseEntity, ToggleEntity):
"""Represents a Switch as a ToggleEntity."""
@ -178,3 +183,25 @@ class BaseToggleEntity(BaseEntity, ToggleEntity):
return
self._attr_is_on = state.state == STATE_ON
class BaseInvertableEntity(BaseEntity):
"""Represents a Switch as an X."""
def __init__(
self,
hass: HomeAssistant,
config_entry_title: str,
domain: str,
invert: bool,
switch_entity_id: str,
unique_id: str,
) -> None:
"""Initialize Switch as an X."""
super().__init__(hass, config_entry_title, domain, switch_entity_id, unique_id)
self._invert_state = invert
@callback
def async_generate_entity_options(self) -> dict[str, Any]:
"""Generate entity options."""
return super().async_generate_entity_options() | {"invert": self._invert_state}

View file

@ -19,7 +19,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import EventStateChangedData
from homeassistant.helpers.typing import EventType
from .entity import BaseEntity
from .const import CONF_INVERT
from .entity import BaseInvertableEntity
async def async_setup_entry(
@ -39,6 +40,7 @@ async def async_setup_entry(
hass,
config_entry.title,
LOCK_DOMAIN,
config_entry.options[CONF_INVERT],
entity_id,
config_entry.entry_id,
)
@ -46,14 +48,14 @@ async def async_setup_entry(
)
class LockSwitch(BaseEntity, LockEntity):
class LockSwitch(BaseInvertableEntity, LockEntity):
"""Represents a Switch as a Lock."""
async def async_lock(self, **kwargs: Any) -> None:
"""Lock the lock."""
await self.hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON if self._invert_state else SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: self._switch_entity_id},
blocking=True,
context=self._context,
@ -63,7 +65,7 @@ class LockSwitch(BaseEntity, LockEntity):
"""Unlock the lock."""
await self.hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
SERVICE_TURN_OFF if self._invert_state else SERVICE_TURN_ON,
{ATTR_ENTITY_ID: self._switch_entity_id},
blocking=True,
context=self._context,
@ -83,4 +85,7 @@ class LockSwitch(BaseEntity, LockEntity):
# Logic is the same as the lock device class for binary sensors
# on means open (unlocked), off means closed (locked)
if self._invert_state:
self._attr_is_locked = state.state == STATE_ON
else:
self._attr_is_locked = state.state != STATE_ON

View file

@ -6,7 +6,23 @@
"description": "Pick a switch that you want to show up in Home Assistant as a light, cover or anything else. The original switch will be hidden.",
"data": {
"entity_id": "Switch",
"invert": "Invert state",
"target_domain": "New Type"
},
"data_description": {
"invert": "Invert state, only supported for cover, lock and valve."
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"invert": "[%key:component::switch_as_x::config::step::user::data::invert%]"
},
"data_description": {
"invert": "[%key:component::switch_as_x::config::step::user::data_description::invert%]"
}
}
}

View file

@ -23,7 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import EventStateChangedData
from homeassistant.helpers.typing import EventType
from .entity import BaseEntity
from .const import CONF_INVERT
from .entity import BaseInvertableEntity
async def async_setup_entry(
@ -43,6 +44,7 @@ async def async_setup_entry(
hass,
config_entry.title,
VALVE_DOMAIN,
config_entry.options[CONF_INVERT],
entity_id,
config_entry.entry_id,
)
@ -50,7 +52,7 @@ async def async_setup_entry(
)
class ValveSwitch(BaseEntity, ValveEntity):
class ValveSwitch(BaseInvertableEntity, ValveEntity):
"""Represents a Switch as a Valve."""
_attr_supported_features = ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE
@ -60,7 +62,7 @@ class ValveSwitch(BaseEntity, ValveEntity):
"""Open the valve."""
await self.hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
SERVICE_TURN_OFF if self._invert_state else SERVICE_TURN_ON,
{ATTR_ENTITY_ID: self._switch_entity_id},
blocking=True,
context=self._context,
@ -70,7 +72,7 @@ class ValveSwitch(BaseEntity, ValveEntity):
"""Close valve."""
await self.hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON if self._invert_state else SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: self._switch_entity_id},
blocking=True,
context=self._context,
@ -88,4 +90,7 @@ class ValveSwitch(BaseEntity, ValveEntity):
):
return
if self._invert_state:
self._attr_is_closed = state.state == STATE_ON
else:
self._attr_is_closed = state.state != STATE_ON

View file

@ -1 +1,39 @@
"""The tests for Switch as X platforms."""
from homeassistant.const import (
STATE_CLOSED,
STATE_LOCKED,
STATE_OFF,
STATE_ON,
STATE_OPEN,
STATE_UNLOCKED,
Platform,
)
PLATFORMS_TO_TEST = (
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.LOCK,
Platform.SIREN,
Platform.VALVE,
)
STATE_MAP = {
False: {
Platform.COVER: {STATE_ON: STATE_OPEN, STATE_OFF: STATE_CLOSED},
Platform.FAN: {STATE_ON: STATE_ON, STATE_OFF: STATE_OFF},
Platform.LIGHT: {STATE_ON: STATE_ON, STATE_OFF: STATE_OFF},
Platform.LOCK: {STATE_ON: STATE_UNLOCKED, STATE_OFF: STATE_LOCKED},
Platform.SIREN: {STATE_ON: STATE_ON, STATE_OFF: STATE_OFF},
Platform.VALVE: {STATE_ON: STATE_OPEN, STATE_OFF: STATE_CLOSED},
},
True: {
Platform.COVER: {STATE_ON: STATE_CLOSED, STATE_OFF: STATE_OPEN},
Platform.FAN: {STATE_ON: STATE_ON, STATE_OFF: STATE_OFF},
Platform.LIGHT: {STATE_ON: STATE_ON, STATE_OFF: STATE_OFF},
Platform.LOCK: {STATE_ON: STATE_LOCKED, STATE_OFF: STATE_UNLOCKED},
Platform.SIREN: {STATE_ON: STATE_ON, STATE_OFF: STATE_OFF},
Platform.VALVE: {STATE_ON: STATE_CLOSED, STATE_OFF: STATE_OPEN},
},
}

View file

@ -5,23 +5,21 @@ from unittest.mock import AsyncMock
import pytest
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.const import CONF_ENTITY_ID, Platform
from homeassistant import config_entries
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.const import CONF_ENTITY_ID, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry
from . import PLATFORMS_TO_TEST, STATE_MAP
PLATFORMS_TO_TEST = (
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.LOCK,
Platform.SIREN,
Platform.VALVE,
)
from tests.common import MockConfigEntry
@pytest.mark.parametrize("target_domain", PLATFORMS_TO_TEST)
@ -41,6 +39,7 @@ async def test_config_flow(
result["flow_id"],
{
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
)
@ -51,6 +50,7 @@ async def test_config_flow(
assert result["data"] == {}
assert result["options"] == {
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
}
assert len(mock_setup_entry.mock_calls) == 1
@ -59,6 +59,7 @@ async def test_config_flow(
assert config_entry.data == {}
assert config_entry.options == {
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
}
@ -96,6 +97,7 @@ async def test_config_flow_registered_entity(
result["flow_id"],
{
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
)
@ -106,6 +108,7 @@ async def test_config_flow_registered_entity(
assert result["data"] == {}
assert result["options"] == {
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
}
assert len(mock_setup_entry.mock_calls) == 1
@ -114,6 +117,7 @@ async def test_config_flow_registered_entity(
assert config_entry.data == {}
assert config_entry.options == {
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
}
@ -125,26 +129,66 @@ async def test_config_flow_registered_entity(
async def test_options(
hass: HomeAssistant,
target_domain: Platform,
mock_setup_entry: AsyncMock,
) -> None:
"""Test reconfiguring."""
switch_state = STATE_ON
hass.states.async_set("switch.ceiling", switch_state)
switch_as_x_config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
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()
state = hass.states.get(f"{target_domain}.abc")
assert state.state == STATE_MAP[True][target_domain][switch_state]
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
assert config_entry
# Switch light has no options flow
with pytest.raises(data_entry_flow.UnknownHandler):
await hass.config_entries.options.async_init(config_entry.entry_id)
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "init"
schema = result["data_schema"].schema
schema_key = next(k for k in schema if k == CONF_INVERT)
assert schema_key.description["suggested_value"] is True
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_INVERT: False,
},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
}
assert config_entry.data == {}
assert config_entry.options == {
CONF_ENTITY_ID: "switch.ceiling",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
}
assert config_entry.title == "ABC"
# Check config entry is reloaded with new options
await hass.async_block_till_done()
# Check the entity was updated, no new entity was created
assert len(hass.states.async_all()) == 2
# Check the state of the entity has changed as expected
state = hass.states.get(f"{target_domain}.abc")
assert state.state == STATE_MAP[False][target_domain][switch_state]

View file

@ -1,7 +1,13 @@
"""Tests for the Switch as X Cover platform."""
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.const import (
CONF_ENTITY_ID,
SERVICE_CLOSE_COVER,
@ -28,9 +34,12 @@ async def test_default_state(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.COVER,
},
title="Garage Door",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -51,9 +60,12 @@ async def test_service_calls(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.COVER,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -120,3 +132,86 @@ async def test_service_calls(hass: HomeAssistant) -> None:
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN
async def test_service_calls_inverted(hass: HomeAssistant) -> None:
"""Test service calls to cover."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: Platform.COVER,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("cover.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "cover.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{CONF_ENTITY_ID: "cover.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{CONF_ENTITY_ID: "cover.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("cover.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("cover.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("cover.decorative_lights").state == STATE_CLOSED

View file

@ -1,7 +1,12 @@
"""Tests for the Switch as X Fan platform."""
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.const import (
CONF_ENTITY_ID,
SERVICE_TOGGLE,
@ -24,9 +29,12 @@ async def test_default_state(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.FAN,
},
title="Wind Machine",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -47,9 +55,95 @@ async def test_service_calls(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.FAN,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("fan.decorative_lights").state == STATE_ON
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "fan.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("fan.decorative_lights").state == STATE_OFF
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "fan.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("fan.decorative_lights").state == STATE_ON
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "fan.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("fan.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("fan.decorative_lights").state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("fan.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("fan.decorative_lights").state == STATE_ON
async def test_service_calls_inverted(hass: HomeAssistant) -> None:
"""Test service calls affecting the switch as fan entity."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: Platform.FAN,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)

View file

@ -6,7 +6,13 @@ from unittest.mock import patch
import pytest
from homeassistant.components.homeassistant import exposed_entities
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
CONF_ENTITY_ID,
STATE_CLOSED,
@ -22,6 +28,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from . import PLATFORMS_TO_TEST
from tests.common import MockConfigEntry
EXPOSE_SETTINGS = {
@ -30,15 +38,6 @@ EXPOSE_SETTINGS = {
"conversation": True,
}
PLATFORMS_TO_TEST = (
Platform.COVER,
Platform.FAN,
Platform.LIGHT,
Platform.LOCK,
Platform.SIREN,
Platform.VALVE,
)
@pytest.mark.parametrize("target_domain", PLATFORMS_TO_TEST)
async def test_config_entry_unregistered_uuid(
@ -52,9 +51,12 @@ async def test_config_entry_unregistered_uuid(
domain=DOMAIN,
options={
CONF_ENTITY_ID: fake_uuid,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
@ -92,9 +94,12 @@ async def test_entity_registry_events(
domain=DOMAIN,
options={
CONF_ENTITY_ID: registry_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
@ -169,9 +174,12 @@ async def test_device_registry_config_entry_1(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -224,9 +232,12 @@ async def test_device_registry_config_entry_2(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -258,9 +269,12 @@ async def test_config_entry_entity_id(
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.abc",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
@ -296,9 +310,12 @@ async def test_config_entry_uuid(hass: HomeAssistant, target_domain: Platform) -
domain=DOMAIN,
options={
CONF_ENTITY_ID: registry_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
@ -331,9 +348,12 @@ async def test_device(hass: HomeAssistant, target_domain: Platform) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -360,9 +380,12 @@ async def test_setup_and_remove_config_entry(
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(switch_as_x_config_entry.entry_id)
@ -409,9 +432,12 @@ async def test_reset_hidden_by(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -445,9 +471,12 @@ async def test_entity_category_inheritance(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -481,9 +510,12 @@ async def test_entity_options(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -494,7 +526,7 @@ async def test_entity_options(
assert entity_entry
assert entity_entry.device_id == switch_entity_entry.device_id
assert entity_entry.options == {
DOMAIN: {"entity_id": switch_entity_entry.entity_id}
DOMAIN: {"entity_id": switch_entity_entry.entity_id, "invert": False},
}
@ -534,9 +566,12 @@ async def test_entity_name(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -550,7 +585,7 @@ async def test_entity_name(
assert entity_entry.name is None
assert entity_entry.original_name is None
assert entity_entry.options == {
DOMAIN: {"entity_id": switch_entity_entry.entity_id}
DOMAIN: {"entity_id": switch_entity_entry.entity_id, "invert": False}
}
@ -592,9 +627,12 @@ async def test_custom_name_1(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -610,7 +648,7 @@ async def test_custom_name_1(
assert entity_entry.name == "Custom entity name"
assert entity_entry.original_name == "Original entity name"
assert entity_entry.options == {
DOMAIN: {"entity_id": switch_entity_entry.entity_id}
DOMAIN: {"entity_id": switch_entity_entry.entity_id, "invert": False}
}
@ -656,9 +694,12 @@ async def test_custom_name_2(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -689,7 +730,7 @@ async def test_custom_name_2(
assert entity_entry.name == "Old custom entity name"
assert entity_entry.original_name == "Original entity name"
assert entity_entry.options == {
DOMAIN: {"entity_id": switch_entity_entry.entity_id}
DOMAIN: {"entity_id": switch_entity_entry.entity_id, "invert": False}
}
@ -719,9 +760,12 @@ async def test_import_expose_settings_1(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -777,9 +821,12 @@ async def test_import_expose_settings_2(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -842,9 +889,12 @@ async def test_restore_expose_settings(
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
switch_as_x_config_entry.add_to_hass(hass)
@ -871,3 +921,80 @@ async def test_restore_expose_settings(
)
for assistant in EXPOSE_SETTINGS:
assert expose_settings[assistant]["should_expose"] == EXPOSE_SETTINGS[assistant]
@pytest.mark.parametrize("target_domain", PLATFORMS_TO_TEST)
async def test_migrate(
hass: HomeAssistant,
target_domain: Platform,
) -> None:
"""Test migration."""
registry = er.async_get(hass)
# Setup the config entry
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=1,
minor_version=1,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Check migration was successful and added invert option
assert config_entry.state == ConfigEntryState.LOADED
assert config_entry.options == {
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: target_domain,
}
assert config_entry.version == SwitchAsXConfigFlowHandler.VERSION
assert config_entry.minor_version == SwitchAsXConfigFlowHandler.MINOR_VERSION
# 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
@pytest.mark.parametrize("target_domain", PLATFORMS_TO_TEST)
async def test_migrate_from_future(
hass: HomeAssistant,
target_domain: Platform,
) -> None:
"""Test migration."""
registry = er.async_get(hass)
# Setup the config entry
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
version=2,
minor_version=1,
)
config_entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Check migration was not successful and did not add invert option
assert config_entry.state == ConfigEntryState.MIGRATION_ERROR
assert config_entry.options == {
CONF_ENTITY_ID: "switch.test",
CONF_TARGET_DOMAIN: target_domain,
}
assert config_entry.version == 2
assert config_entry.minor_version == 1
# Check the state and entity registry entry are not present
assert hass.states.get(f"{target_domain}.abc") is None
assert registry.async_get(f"{target_domain}.abc") is None

View file

@ -11,7 +11,12 @@ from homeassistant.components.light import (
ColorMode,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.const import (
CONF_ENTITY_ID,
SERVICE_TOGGLE,
@ -34,9 +39,12 @@ async def test_default_state(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.LIGHT,
},
title="Christmas Tree Lights",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -64,9 +72,12 @@ async def test_light_service_calls(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.LIGHT,
},
title="decorative_lights",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -118,9 +129,112 @@ async def test_switch_service_calls(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.LIGHT,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("light.decorative_lights").state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("light.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("light.decorative_lights").state == STATE_ON
async def test_light_service_calls_inverted(hass: HomeAssistant) -> None:
"""Test service calls to light."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: Platform.LIGHT,
},
title="decorative_lights",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("light.decorative_lights").state == STATE_ON
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "light.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("light.decorative_lights").state == STATE_OFF
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "light.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("light.decorative_lights").state == STATE_ON
assert (
hass.states.get("light.decorative_lights").attributes.get(ATTR_COLOR_MODE)
== ColorMode.ONOFF
)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "light.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("light.decorative_lights").state == STATE_OFF
async def test_switch_service_calls_inverted(hass: HomeAssistant) -> None:
"""Test service calls to switch."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: Platform.LIGHT,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)

View file

@ -1,7 +1,12 @@
"""Tests for the Switch as X Lock platform."""
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.const import (
CONF_ENTITY_ID,
SERVICE_LOCK,
@ -28,9 +33,12 @@ async def test_default_state(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.LOCK,
},
title="candy_jar",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -50,9 +58,12 @@ async def test_service_calls(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.LOCK,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -109,3 +120,76 @@ async def test_service_calls(hass: HomeAssistant) -> None:
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("lock.decorative_lights").state == STATE_LOCKED
async def test_service_calls_inverted(hass: HomeAssistant) -> None:
"""Test service calls affecting the switch as lock entity."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: Platform.LOCK,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("lock.decorative_lights").state == STATE_LOCKED
await hass.services.async_call(
LOCK_DOMAIN,
SERVICE_LOCK,
{CONF_ENTITY_ID: "lock.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("lock.decorative_lights").state == STATE_LOCKED
await hass.services.async_call(
LOCK_DOMAIN,
SERVICE_UNLOCK,
{CONF_ENTITY_ID: "lock.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("lock.decorative_lights").state == STATE_UNLOCKED
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("lock.decorative_lights").state == STATE_UNLOCKED
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("lock.decorative_lights").state == STATE_LOCKED
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("lock.decorative_lights").state == STATE_UNLOCKED

View file

@ -1,7 +1,12 @@
"""Tests for the Switch as X Siren platform."""
from homeassistant.components.siren import DOMAIN as SIREN_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.const import (
CONF_ENTITY_ID,
SERVICE_TOGGLE,
@ -24,9 +29,12 @@ async def test_default_state(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.SIREN,
},
title="Noise Maker",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -47,9 +55,95 @@ async def test_service_calls(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.SIREN,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("siren.decorative_lights").state == STATE_ON
await hass.services.async_call(
SIREN_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "siren.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("siren.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SIREN_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "siren.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("siren.decorative_lights").state == STATE_ON
await hass.services.async_call(
SIREN_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "siren.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("siren.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("siren.decorative_lights").state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("siren.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("siren.decorative_lights").state == STATE_ON
async def test_service_calls_inverted(hass: HomeAssistant) -> None:
"""Test service calls affecting the switch as siren entity."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: Platform.SIREN,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)

View file

@ -1,6 +1,11 @@
"""Tests for the Switch as X Valve platform."""
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.switch_as_x.const import CONF_TARGET_DOMAIN, DOMAIN
from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler
from homeassistant.components.switch_as_x.const import (
CONF_INVERT,
CONF_TARGET_DOMAIN,
DOMAIN,
)
from homeassistant.components.valve import DOMAIN as VALVE_DOMAIN
from homeassistant.const import (
CONF_ENTITY_ID,
@ -28,9 +33,12 @@ async def test_default_state(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.test",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.VALVE,
},
title="Garage Door",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -51,9 +59,12 @@ async def test_service_calls(hass: HomeAssistant) -> None:
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: False,
CONF_TARGET_DOMAIN: Platform.VALVE,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
@ -120,3 +131,86 @@ async def test_service_calls(hass: HomeAssistant) -> None:
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("valve.decorative_lights").state == STATE_OPEN
async def test_service_calls_inverted(hass: HomeAssistant) -> None:
"""Test service calls to valve."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_INVERT: True,
CONF_TARGET_DOMAIN: Platform.VALVE,
},
title="Title is ignored",
version=SwitchAsXConfigFlowHandler.VERSION,
minor_version=SwitchAsXConfigFlowHandler.MINOR_VERSION,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("valve.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
VALVE_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "valve.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("valve.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
VALVE_DOMAIN,
SERVICE_OPEN_VALVE,
{CONF_ENTITY_ID: "valve.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("valve.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
VALVE_DOMAIN,
SERVICE_CLOSE_VALVE,
{CONF_ENTITY_ID: "valve.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("valve.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("valve.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("valve.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "switch.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("valve.decorative_lights").state == STATE_CLOSED