From 312812dd8be9ca9a006e039fa8b84e7b86a0a796 Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:02:18 -0400 Subject: [PATCH] Fix variables in icon, picture, and name for state based template entities (#154994) --- .../components/template/template_entity.py | 8 +- tests/components/template/test_blueprint.py | 84 +++++++++++++++++++ tests/components/template/test_config.py | 58 +++++++++++++ .../test_init_attribute_variables.yaml | 31 +++++++ 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 tests/testing_config/blueprints/template/test_init_attribute_variables.yaml diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index f4e1257e36b6..ca3841dc9617 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -200,7 +200,13 @@ class TemplateEntity(AbstractTemplateEntity): """Name of this state.""" return "" - variables = {"this": DummyState()} + # Render the current variables and add a dummy this variable to them. + variables = ( + self._run_variables + if isinstance(self._run_variables, dict) + else self._run_variables.async_render(self.hass, {}) + ) + variables = {"this": DummyState(), **variables} # Try to render the name as it can influence the entity ID self._attr_name = None diff --git a/tests/components/template/test_blueprint.py b/tests/components/template/test_blueprint.py index fd45c3b008b3..469e3df0ae06 100644 --- a/tests/components/template/test_blueprint.py +++ b/tests/components/template/test_blueprint.py @@ -228,6 +228,90 @@ async def test_reload_template_when_blueprint_changes(hass: HomeAssistant) -> No assert not_inverted.state == "on" +async def test_init_attribute_variables_from_blueprint(hass: HomeAssistant) -> None: + """Test a state based blueprint initializes icon, name, and picture with variables.""" + blueprint = "test_init_attribute_variables.yaml" + source = "switch.foo" + entity_id = "sensor.foo" + hass.states.async_set(source, "on", {"friendly_name": "Foo"}) + config = { + DOMAIN: [ + { + "use_blueprint": { + "path": blueprint, + "input": {"switch": source}, + }, + } + ], + } + assert await async_setup_component( + hass, + DOMAIN, + config, + ) + await hass.async_block_till_done() + + # Check initial state + sensor = hass.states.get(entity_id) + assert sensor + assert sensor.state == "True" + assert sensor.attributes["icon"] == "mdi:lightbulb" + assert sensor.attributes["entity_picture"] == "on.png" + assert sensor.attributes["friendly_name"] == "Foo" + assert sensor.attributes["extra"] == "ab" + + hass.states.async_set(source, "off", {"friendly_name": "Foo"}) + await hass.async_block_till_done() + + # Check to see that the template light works + sensor = hass.states.get(entity_id) + assert sensor + assert sensor.state == "False" + assert sensor.attributes["icon"] == "mdi:lightbulb-off" + assert sensor.attributes["entity_picture"] == "off.png" + assert sensor.attributes["friendly_name"] == "Foo" + assert sensor.attributes["extra"] == "ab" + + # Reload the templates without any change, but with updated blueprint + blueprint_config = yaml_util.load_yaml( + pathlib.Path("tests/testing_config/blueprints/template/") / blueprint + ) + blueprint_config["variables"]["extraa"] = "c" + blueprint_config["sensor"]["variables"]["extrab"] = "d" + with ( + patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value=config, + ), + patch( + "homeassistant.components.blueprint.models.yaml_util.load_yaml_dict", + autospec=True, + return_value=blueprint_config, + ), + ): + await hass.services.async_call(DOMAIN, SERVICE_RELOAD, blocking=True) + + sensor = hass.states.get(entity_id) + assert sensor + assert sensor.state == "False" + assert sensor.attributes["icon"] == "mdi:lightbulb-off" + assert sensor.attributes["entity_picture"] == "off.png" + assert sensor.attributes["friendly_name"] == "Foo" + assert sensor.attributes["extra"] == "cd" + + hass.states.async_set(source, "on", {"friendly_name": "Foo"}) + await hass.async_block_till_done() + + sensor = hass.states.get(entity_id) + assert sensor + assert sensor.state == "True" + assert sensor.attributes["icon"] == "mdi:lightbulb" + assert sensor.attributes["entity_picture"] == "on.png" + assert sensor.attributes["friendly_name"] == "Foo" + assert sensor.attributes["extra"] == "cd" + + @pytest.mark.parametrize( ("blueprint"), ["test_event_sensor.yaml", "test_event_sensor_legacy_schema.yaml"], diff --git a/tests/components/template/test_config.py b/tests/components/template/test_config.py index 88d6a2554f53..1238e1fcc16c 100644 --- a/tests/components/template/test_config.py +++ b/tests/components/template/test_config.py @@ -5,6 +5,7 @@ from __future__ import annotations import pytest import voluptuous as vol +from homeassistant.components.template import DOMAIN from homeassistant.components.template.config import ( CONFIG_SECTION_SCHEMA, async_validate_config_section, @@ -12,6 +13,7 @@ from homeassistant.components.template.config import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.template import Template +from homeassistant.setup import async_setup_component @pytest.mark.parametrize( @@ -256,3 +258,59 @@ async def test_combined_trigger_variables( assert root_variables.as_dict() == expected_root variables: ScriptVariables = validated["binary_sensor"][0].get("variables", empty) assert variables.as_dict() == expected_entity + + +async def test_state_init_attribute_variables( + hass: HomeAssistant, +) -> None: + """Test a state based template entity initializes icon, name, and picture with variables.""" + source = "switch.foo" + entity_id = "sensor.foo" + + hass.states.async_set(source, "on", {"friendly_name": "Foo"}) + config = { + "template": [ + { + "variables": { + "switch": "switch.foo", + "on_icon": "mdi:lightbulb", + "on_picture": "on.png", + }, + "sensor": { + "variables": { + "off_icon": "mdi:lightbulb-off", + "off_picture": "off.png", + }, + "name": "{{ state_attr(switch, 'friendly_name') }}", + "icon": "{{ on_icon if is_state(switch, 'on') else off_icon }}", + "picture": "{{ on_picture if is_state(switch, 'on') else off_picture }}", + "state": "{{ is_state(switch, 'on') }}", + }, + } + ], + } + assert await async_setup_component( + hass, + DOMAIN, + config, + ) + await hass.async_block_till_done() + + # Check initial state + sensor = hass.states.get(entity_id) + assert sensor + assert sensor.state == "True" + assert sensor.attributes["icon"] == "mdi:lightbulb" + assert sensor.attributes["entity_picture"] == "on.png" + assert sensor.attributes["friendly_name"] == "Foo" + + hass.states.async_set(source, "off", {"friendly_name": "Foo"}) + await hass.async_block_till_done() + + # Check to see that the template light works + sensor = hass.states.get(entity_id) + assert sensor + assert sensor.state == "False" + assert sensor.attributes["icon"] == "mdi:lightbulb-off" + assert sensor.attributes["entity_picture"] == "off.png" + assert sensor.attributes["friendly_name"] == "Foo" diff --git a/tests/testing_config/blueprints/template/test_init_attribute_variables.yaml b/tests/testing_config/blueprints/template/test_init_attribute_variables.yaml new file mode 100644 index 000000000000..7729b543eaff --- /dev/null +++ b/tests/testing_config/blueprints/template/test_init_attribute_variables.yaml @@ -0,0 +1,31 @@ +blueprint: + name: Switch to light + domain: template + input: + switch: + name: Switch + description: The switch which should be converted + selector: + entity: + multiple: false + filter: + - domain: switch + default: null + +variables: + switch: !input switch + on_icon: mdi:lightbulb + on_picture: "on.png" + extraa: "a" + +sensor: + variables: + off_icon: mdi:lightbulb-off + off_picture: "off.png" + extrab: "b" + name: "{{ state_attr(switch, 'friendly_name') }}" + icon: "{{ on_icon if is_state(switch, 'on') else off_icon }}" + picture: "{{ on_picture if is_state(switch, 'on') else off_picture }}" + state: "{{ is_state(switch, 'on') }}" + attributes: + extra: "{{ extraa ~ extrab }}"