"""The tests for the Template select platform.""" from typing import Any import pytest from syrupy.assertion import SnapshotAssertion from homeassistant import setup from homeassistant.components import select, template from homeassistant.components.input_select import ( ATTR_OPTION as INPUT_SELECT_ATTR_OPTION, ATTR_OPTIONS as INPUT_SELECT_ATTR_OPTIONS, DOMAIN as INPUT_SELECT_DOMAIN, SERVICE_SELECT_OPTION as INPUT_SELECT_SERVICE_SELECT_OPTION, SERVICE_SET_OPTIONS, ) from homeassistant.components.select import ( ATTR_OPTION as SELECT_ATTR_OPTION, ATTR_OPTIONS as SELECT_ATTR_OPTIONS, DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION as SELECT_SERVICE_SELECT_OPTION, ) from homeassistant.components.template import DOMAIN from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, CONF_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import Context, HomeAssistant, ServiceCall from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from .conftest import ConfigurationStyle from tests.common import MockConfigEntry, assert_setup_component, async_capture_events _TEST_OBJECT_ID = "template_select" _TEST_SELECT = f"select.{_TEST_OBJECT_ID}" # Represent for select's current_option _OPTION_INPUT_SELECT = "input_select.option" async def async_setup_modern_format( hass: HomeAssistant, count: int, select_config: dict[str, Any] ) -> None: """Do setup of select integration via new format.""" config = {"template": {"select": select_config}} with assert_setup_component(count, template.DOMAIN): assert await async_setup_component( hass, template.DOMAIN, config, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @pytest.fixture async def setup_select( hass: HomeAssistant, count: int, style: ConfigurationStyle, select_config: dict[str, Any], ) -> None: """Do setup of select integration.""" if style == ConfigurationStyle.MODERN: await async_setup_modern_format( hass, count, {"name": _TEST_OBJECT_ID, **select_config} ) async def test_setup_config_entry( hass: HomeAssistant, snapshot: SnapshotAssertion, ) -> None: """Test the config flow.""" template_config_entry = MockConfigEntry( data={}, domain=DOMAIN, options={ "name": "My template", "template_type": "select", "state": "{{ 'on' }}", "options": "{{ ['off', 'on', 'auto'] }}", }, title="My template", ) template_config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(template_config_entry.entry_id) await hass.async_block_till_done() state = hass.states.get("select.my_template") assert state is not None assert state == snapshot async def test_missing_optional_config(hass: HomeAssistant) -> None: """Test: missing optional template is ok.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( hass, "template", { "template": { "select": { "state": "{{ 'a' }}", "select_option": {"service": "script.select_option"}, "options": "{{ ['a', 'b'] }}", } } }, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() _verify(hass, "a", ["a", "b"]) async def test_multiple_configs(hass: HomeAssistant) -> None: """Test: multiple select entities get created.""" with assert_setup_component(1, "template"): assert await setup.async_setup_component( hass, "template", { "template": { "select": [ { "state": "{{ 'a' }}", "select_option": {"service": "script.select_option"}, "options": "{{ ['a', 'b'] }}", }, { "state": "{{ 'a' }}", "select_option": {"service": "script.select_option"}, "options": "{{ ['a', 'b'] }}", }, ] } }, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() _verify(hass, "a", ["a", "b"]) _verify(hass, "a", ["a", "b"], f"{_TEST_SELECT}_2") async def test_missing_required_keys(hass: HomeAssistant) -> None: """Test: missing required fields will fail.""" with assert_setup_component(0, "template"): assert await setup.async_setup_component( hass, "template", { "template": { "select": { "select_option": {"service": "script.select_option"}, "options": "{{ ['a', 'b'] }}", } } }, ) with assert_setup_component(0, "select"): assert await setup.async_setup_component( hass, "select", { "template": { "select": { "state": "{{ 'a' }}", "select_option": {"service": "script.select_option"}, } } }, ) with assert_setup_component(0, "select"): assert await setup.async_setup_component( hass, "select", { "template": { "select": { "state": "{{ 'a' }}", "options": "{{ ['a', 'b'] }}", } } }, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() assert hass.states.async_all("select") == [] async def test_templates_with_entities( hass: HomeAssistant, entity_registry: er.EntityRegistry, calls: list[ServiceCall] ) -> None: """Test templates with values from other entities.""" with assert_setup_component(1, "input_select"): assert await setup.async_setup_component( hass, "input_select", { "input_select": { "option": { "options": ["a", "b"], "initial": "a", "name": "Option", }, } }, ) with assert_setup_component(1, "template"): assert await setup.async_setup_component( hass, "template", { "template": { "unique_id": "b", "select": { "state": f"{{{{ states('{_OPTION_INPUT_SELECT}') }}}}", "options": f"{{{{ state_attr('{_OPTION_INPUT_SELECT}', '{INPUT_SELECT_ATTR_OPTIONS}') }}}}", "select_option": [ { "service": "input_select.select_option", "data_template": { "entity_id": _OPTION_INPUT_SELECT, "option": "{{ option }}", }, }, { "service": "test.automation", "data_template": { "action": "select_option", "caller": "{{ this.entity_id }}", "option": "{{ option }}", }, }, ], "optimistic": True, "unique_id": "a", }, } }, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() entry = entity_registry.async_get(_TEST_SELECT) assert entry assert entry.unique_id == "b-a" _verify(hass, "a", ["a", "b"]) await hass.services.async_call( INPUT_SELECT_DOMAIN, INPUT_SELECT_SERVICE_SELECT_OPTION, {CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTION: "b"}, blocking=True, ) await hass.async_block_till_done() _verify(hass, "b", ["a", "b"]) await hass.services.async_call( INPUT_SELECT_DOMAIN, SERVICE_SET_OPTIONS, { CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTIONS: ["a", "b", "c"], }, blocking=True, ) await hass.async_block_till_done() _verify(hass, "b", ["a", "b", "c"]) await hass.services.async_call( SELECT_DOMAIN, SELECT_SERVICE_SELECT_OPTION, {CONF_ENTITY_ID: _TEST_SELECT, SELECT_ATTR_OPTION: "c"}, blocking=True, ) _verify(hass, "c", ["a", "b", "c"]) # Check this variable can be used in set_value script assert len(calls) == 1 assert calls[-1].data["action"] == "select_option" assert calls[-1].data["caller"] == _TEST_SELECT assert calls[-1].data["option"] == "c" async def test_trigger_select(hass: HomeAssistant) -> None: """Test trigger based template select.""" events = async_capture_events(hass, "test_number_event") action_events = async_capture_events(hass, "action_event") assert await setup.async_setup_component( hass, "template", { "template": [ {"invalid": "config"}, # Config after invalid should still be set up { "unique_id": "listening-test-event", "trigger": {"platform": "event", "event_type": "test_event"}, "variables": {"beer": "{{ trigger.event.data.beer }}"}, "action": [ {"event": "action_event", "event_data": {"beer": "{{ beer }}"}} ], "select": [ { "name": "Hello Name", "unique_id": "hello_name-id", "state": "{{ trigger.event.data.beer }}", "options": "{{ trigger.event.data.beers }}", "select_option": { "event": "test_number_event", "event_data": { "entity_id": "{{ this.entity_id }}", "beer": "{{ beer }}", }, }, "optimistic": True, }, ], }, ], }, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() state = hass.states.get("select.hello_name") assert state is not None assert state.state == STATE_UNKNOWN context = Context() hass.bus.async_fire( "test_event", {"beer": "duff", "beers": ["duff", "alamo"]}, context=context ) await hass.async_block_till_done() state = hass.states.get("select.hello_name") assert state is not None assert state.state == "duff" assert state.attributes["options"] == ["duff", "alamo"] assert len(action_events) == 1 assert action_events[0].event_type == "action_event" beer = action_events[0].data.get("beer") assert beer is not None assert beer == "duff" await hass.services.async_call( SELECT_DOMAIN, SELECT_SERVICE_SELECT_OPTION, {CONF_ENTITY_ID: "select.hello_name", SELECT_ATTR_OPTION: "alamo"}, blocking=True, ) assert len(events) == 1 assert events[0].event_type == "test_number_event" entity_id = events[0].data.get("entity_id") assert entity_id is not None assert entity_id == "select.hello_name" beer = events[0].data.get("beer") assert beer is not None assert beer == "duff" def _verify( hass: HomeAssistant, expected_current_option: str, expected_options: list[str], entity_name: str = _TEST_SELECT, ) -> None: """Verify select's state.""" state = hass.states.get(entity_name) attributes = state.attributes assert state.state == str(expected_current_option) assert attributes.get(SELECT_ATTR_OPTIONS) == expected_options async def test_template_icon_with_entities(hass: HomeAssistant) -> None: """Test templates with values from other entities.""" with assert_setup_component(1, "input_select"): assert await setup.async_setup_component( hass, "input_select", { "input_select": { "option": { "options": ["a", "b"], "initial": "a", "name": "Option", }, } }, ) with assert_setup_component(1, "template"): assert await setup.async_setup_component( hass, "template", { "template": { "unique_id": "b", "select": { "state": f"{{{{ states('{_OPTION_INPUT_SELECT}') }}}}", "options": f"{{{{ state_attr('{_OPTION_INPUT_SELECT}', '{INPUT_SELECT_ATTR_OPTIONS}') }}}}", "select_option": { "service": "input_select.select_option", "data": { "entity_id": _OPTION_INPUT_SELECT, "option": "{{ option }}", }, }, "optimistic": True, "unique_id": "a", "icon": f"{{% if (states('{_OPTION_INPUT_SELECT}') == 'a') %}}mdi:greater{{% else %}}mdi:less{{% endif %}}", }, } }, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() state = hass.states.get(_TEST_SELECT) assert state.state == "a" assert state.attributes[ATTR_ICON] == "mdi:greater" await hass.services.async_call( INPUT_SELECT_DOMAIN, INPUT_SELECT_SERVICE_SELECT_OPTION, {CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTION: "b"}, blocking=True, ) await hass.async_block_till_done() state = hass.states.get(_TEST_SELECT) assert state.state == "b" assert state.attributes[ATTR_ICON] == "mdi:less" async def test_template_icon_with_trigger(hass: HomeAssistant) -> None: """Test trigger based template select.""" with assert_setup_component(1, "input_select"): assert await setup.async_setup_component( hass, "input_select", { "input_select": { "option": { "options": ["a", "b"], "initial": "a", "name": "Option", }, } }, ) assert await setup.async_setup_component( hass, "template", { "template": { "trigger": {"platform": "state", "entity_id": _OPTION_INPUT_SELECT}, "select": { "unique_id": "b", "state": "{{ trigger.to_state.state }}", "options": f"{{{{ state_attr('{_OPTION_INPUT_SELECT}', '{INPUT_SELECT_ATTR_OPTIONS}') }}}}", "select_option": { "service": "input_select.select_option", "data": { "entity_id": _OPTION_INPUT_SELECT, "option": "{{ option }}", }, }, "optimistic": True, "icon": "{% if (trigger.to_state.state or '') == 'a' %}mdi:greater{% else %}mdi:less{% endif %}", }, }, }, ) await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() await hass.services.async_call( INPUT_SELECT_DOMAIN, INPUT_SELECT_SERVICE_SELECT_OPTION, {CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTION: "b"}, blocking=True, ) await hass.async_block_till_done() state = hass.states.get(_TEST_SELECT) assert state is not None assert state.state == "b" assert state.attributes[ATTR_ICON] == "mdi:less" await hass.services.async_call( INPUT_SELECT_DOMAIN, INPUT_SELECT_SERVICE_SELECT_OPTION, {CONF_ENTITY_ID: _OPTION_INPUT_SELECT, INPUT_SELECT_ATTR_OPTION: "a"}, blocking=True, ) await hass.async_block_till_done() state = hass.states.get(_TEST_SELECT) assert state.state == "a" assert state.attributes[ATTR_ICON] == "mdi:greater" async def test_device_id( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test for device for select template.""" device_config_entry = MockConfigEntry() device_config_entry.add_to_hass(hass) device_entry = device_registry.async_get_or_create( config_entry_id=device_config_entry.entry_id, identifiers={("test", "identifier_test")}, connections={("mac", "30:31:32:33:34:35")}, ) await hass.async_block_till_done() assert device_entry is not None assert device_entry.id is not None template_config_entry = MockConfigEntry( data={}, domain=DOMAIN, options={ "name": "My template", "template_type": "select", "state": "{{ 'on' }}", "options": "{{ ['off', 'on', 'auto'] }}", "device_id": device_entry.id, }, title="My template", ) template_config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(template_config_entry.entry_id) await hass.async_block_till_done() template_entity = entity_registry.async_get("select.my_template") assert template_entity is not None assert template_entity.device_id == device_entry.id @pytest.mark.parametrize( ("count", "select_config"), [ ( 1, { "state": "{{ 'b' }}", "select_option": [], "options": "{{ ['a', 'b'] }}", "optimistic": True, }, ) ], ) @pytest.mark.parametrize( "style", [ ConfigurationStyle.MODERN, ], ) async def test_empty_action_config(hass: HomeAssistant, setup_select) -> None: """Test configuration with empty script.""" await hass.services.async_call( select.DOMAIN, select.SERVICE_SELECT_OPTION, {ATTR_ENTITY_ID: _TEST_SELECT, "option": "a"}, blocking=True, ) state = hass.states.get(_TEST_SELECT) assert state.state == "a"