diff --git a/homeassistant/components/switch/trigger.py b/homeassistant/components/switch/trigger.py index 6797cad0f6b..cb1254f8c07 100644 --- a/homeassistant/components/switch/trigger.py +++ b/homeassistant/components/switch/trigger.py @@ -1,14 +1,18 @@ """Provides triggers for switch platform.""" +from homeassistant.components.input_boolean import DOMAIN as INPUT_BOOLEAN_DOMAIN from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant +from homeassistant.helpers.automation import DomainSpec from homeassistant.helpers.trigger import Trigger, make_entity_target_state_trigger from .const import DOMAIN +SWITCH_DOMAIN_SPECS = {DOMAIN: DomainSpec(), INPUT_BOOLEAN_DOMAIN: DomainSpec()} + TRIGGERS: dict[str, type[Trigger]] = { - "turned_on": make_entity_target_state_trigger(DOMAIN, STATE_ON), - "turned_off": make_entity_target_state_trigger(DOMAIN, STATE_OFF), + "turned_on": make_entity_target_state_trigger(SWITCH_DOMAIN_SPECS, STATE_ON), + "turned_off": make_entity_target_state_trigger(SWITCH_DOMAIN_SPECS, STATE_OFF), } diff --git a/homeassistant/components/switch/triggers.yaml b/homeassistant/components/switch/triggers.yaml index dc9e3401867..98cc334d8f5 100644 --- a/homeassistant/components/switch/triggers.yaml +++ b/homeassistant/components/switch/triggers.yaml @@ -1,7 +1,8 @@ .trigger_common: &trigger_common target: entity: - domain: switch + - domain: switch + - domain: input_boolean fields: behavior: required: true diff --git a/tests/components/switch/test_trigger.py b/tests/components/switch/test_trigger.py index 3a5e88883c5..47a1578c01f 100644 --- a/tests/components/switch/test_trigger.py +++ b/tests/components/switch/test_trigger.py @@ -5,11 +5,12 @@ from typing import Any import pytest from homeassistant.components.switch import DOMAIN -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.const import CONF_ENTITY_ID, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, ServiceCall from tests.components.common import ( TriggerStateDescription, + arm_trigger, assert_trigger_behavior_any, assert_trigger_behavior_first, assert_trigger_behavior_last, @@ -19,6 +20,19 @@ from tests.components.common import ( target_entities, ) +TRIGGER_STATES = [ + *parametrize_trigger_states( + trigger="switch.turned_off", + target_states=[STATE_OFF], + other_states=[STATE_ON], + ), + *parametrize_trigger_states( + trigger="switch.turned_on", + target_states=[STATE_ON], + other_states=[STATE_OFF], + ), +] + @pytest.fixture async def target_switches(hass: HomeAssistant) -> dict[str, list[str]]: @@ -26,6 +40,12 @@ async def target_switches(hass: HomeAssistant) -> dict[str, list[str]]: return await target_entities(hass, DOMAIN) +@pytest.fixture +async def target_input_booleans(hass: HomeAssistant) -> dict[str, list[str]]: + """Create multiple input_boolean entities associated with different targets.""" + return await target_entities(hass, "input_boolean") + + @pytest.mark.parametrize( "trigger_key", [ @@ -40,6 +60,9 @@ async def test_switch_triggers_gated_by_labs_flag( await assert_trigger_gated_by_labs_flag(hass, caplog, trigger_key) +# --- Switch domain tests --- + + @pytest.mark.usefixtures("enable_labs_preview_features") @pytest.mark.parametrize( ("trigger_target_config", "entity_id", "entities_in_target"), @@ -47,18 +70,7 @@ async def test_switch_triggers_gated_by_labs_flag( ) @pytest.mark.parametrize( ("trigger", "trigger_options", "states"), - [ - *parametrize_trigger_states( - trigger="switch.turned_off", - target_states=[STATE_OFF], - other_states=[STATE_ON], - ), - *parametrize_trigger_states( - trigger="switch.turned_on", - target_states=[STATE_ON], - other_states=[STATE_OFF], - ), - ], + TRIGGER_STATES, ) async def test_switch_state_trigger_behavior_any( hass: HomeAssistant, @@ -92,18 +104,7 @@ async def test_switch_state_trigger_behavior_any( ) @pytest.mark.parametrize( ("trigger", "trigger_options", "states"), - [ - *parametrize_trigger_states( - trigger="switch.turned_off", - target_states=[STATE_OFF], - other_states=[STATE_ON], - ), - *parametrize_trigger_states( - trigger="switch.turned_on", - target_states=[STATE_ON], - other_states=[STATE_OFF], - ), - ], + TRIGGER_STATES, ) async def test_switch_state_trigger_behavior_first( hass: HomeAssistant, @@ -137,18 +138,7 @@ async def test_switch_state_trigger_behavior_first( ) @pytest.mark.parametrize( ("trigger", "trigger_options", "states"), - [ - *parametrize_trigger_states( - trigger="switch.turned_off", - target_states=[STATE_OFF], - other_states=[STATE_ON], - ), - *parametrize_trigger_states( - trigger="switch.turned_on", - target_states=[STATE_ON], - other_states=[STATE_OFF], - ), - ], + TRIGGER_STATES, ) async def test_switch_state_trigger_behavior_last( hass: HomeAssistant, @@ -173,3 +163,146 @@ async def test_switch_state_trigger_behavior_last( trigger_options=trigger_options, states=states, ) + + +# --- Input boolean domain tests --- + + +@pytest.mark.usefixtures("enable_labs_preview_features") +@pytest.mark.parametrize( + ("trigger_target_config", "entity_id", "entities_in_target"), + parametrize_target_entities("input_boolean"), +) +@pytest.mark.parametrize( + ("trigger", "trigger_options", "states"), + TRIGGER_STATES, +) +async def test_input_boolean_state_trigger_behavior_any( + hass: HomeAssistant, + service_calls: list[ServiceCall], + target_input_booleans: dict[str, list[str]], + trigger_target_config: dict, + entity_id: str, + entities_in_target: int, + trigger: str, + trigger_options: dict[str, Any], + states: list[TriggerStateDescription], +) -> None: + """Test that the switch trigger fires when any input_boolean state changes.""" + await assert_trigger_behavior_any( + hass, + service_calls=service_calls, + target_entities=target_input_booleans, + trigger_target_config=trigger_target_config, + entity_id=entity_id, + entities_in_target=entities_in_target, + trigger=trigger, + trigger_options=trigger_options, + states=states, + ) + + +@pytest.mark.usefixtures("enable_labs_preview_features") +@pytest.mark.parametrize( + ("trigger_target_config", "entity_id", "entities_in_target"), + parametrize_target_entities("input_boolean"), +) +@pytest.mark.parametrize( + ("trigger", "trigger_options", "states"), + TRIGGER_STATES, +) +async def test_input_boolean_state_trigger_behavior_first( + hass: HomeAssistant, + service_calls: list[ServiceCall], + target_input_booleans: dict[str, list[str]], + trigger_target_config: dict, + entity_id: str, + entities_in_target: int, + trigger: str, + trigger_options: dict[str, Any], + states: list[TriggerStateDescription], +) -> None: + """Test that the switch trigger fires when the first input_boolean changes.""" + await assert_trigger_behavior_first( + hass, + service_calls=service_calls, + target_entities=target_input_booleans, + trigger_target_config=trigger_target_config, + entity_id=entity_id, + entities_in_target=entities_in_target, + trigger=trigger, + trigger_options=trigger_options, + states=states, + ) + + +@pytest.mark.usefixtures("enable_labs_preview_features") +@pytest.mark.parametrize( + ("trigger_target_config", "entity_id", "entities_in_target"), + parametrize_target_entities("input_boolean"), +) +@pytest.mark.parametrize( + ("trigger", "trigger_options", "states"), + TRIGGER_STATES, +) +async def test_input_boolean_state_trigger_behavior_last( + hass: HomeAssistant, + service_calls: list[ServiceCall], + target_input_booleans: dict[str, list[str]], + trigger_target_config: dict, + entity_id: str, + entities_in_target: int, + trigger: str, + trigger_options: dict[str, Any], + states: list[TriggerStateDescription], +) -> None: + """Test that the switch trigger fires when the last input_boolean changes.""" + await assert_trigger_behavior_last( + hass, + service_calls=service_calls, + target_entities=target_input_booleans, + trigger_target_config=trigger_target_config, + entity_id=entity_id, + entities_in_target=entities_in_target, + trigger=trigger, + trigger_options=trigger_options, + states=states, + ) + + +# --- Cross-domain test --- + + +@pytest.mark.usefixtures("enable_labs_preview_features") +async def test_switch_trigger_fires_for_both_domains( + hass: HomeAssistant, + service_calls: list[ServiceCall], +) -> None: + """Test that the switch trigger fires for both switch and input_boolean entities.""" + entity_id_switch = "switch.test_switch" + entity_id_input_boolean = "input_boolean.test_input_boolean" + + hass.states.async_set(entity_id_switch, STATE_OFF) + hass.states.async_set(entity_id_input_boolean, STATE_OFF) + await hass.async_block_till_done() + + await arm_trigger( + hass, + "switch.turned_on", + {}, + {CONF_ENTITY_ID: [entity_id_switch, entity_id_input_boolean]}, + ) + + # switch entity changes - should trigger + hass.states.async_set(entity_id_switch, STATE_ON) + await hass.async_block_till_done() + assert len(service_calls) == 1 + assert service_calls[0].data[CONF_ENTITY_ID] == entity_id_switch + service_calls.clear() + + # input_boolean entity changes - should also trigger + hass.states.async_set(entity_id_input_boolean, STATE_ON) + await hass.async_block_till_done() + assert len(service_calls) == 1 + assert service_calls[0].data[CONF_ENTITY_ID] == entity_id_input_boolean + service_calls.clear()