From 24b7cf261c2ff0bb75b344a34cd2f75c724cfc92 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 19 Oct 2025 02:24:58 +0300 Subject: [PATCH] Streamline template tests (#154586) --- .../template/extensions/test_base64.py | 18 +- .../template/extensions/test_collection.py | 176 +- .../template/extensions/test_crypto.py | 51 +- .../helpers/template/extensions/test_math.py | 182 +- .../helpers/template/extensions/test_regex.py | 249 +-- .../template/extensions/test_string.py | 85 +- tests/helpers/template/test_init.py | 1628 ++++++----------- tests/helpers/template/test_render_info.py | 38 +- 8 files changed, 851 insertions(+), 1576 deletions(-) diff --git a/tests/helpers/template/extensions/test_base64.py b/tests/helpers/template/extensions/test_base64.py index b0c1fb35134..f1679633431 100644 --- a/tests/helpers/template/extensions/test_base64.py +++ b/tests/helpers/template/extensions/test_base64.py @@ -5,7 +5,8 @@ from __future__ import annotations import pytest from homeassistant.core import HomeAssistant -from homeassistant.helpers import template + +from tests.helpers.template.helpers import render @pytest.mark.parametrize( @@ -18,26 +19,19 @@ from homeassistant.helpers import template ) def test_base64_encode(hass: HomeAssistant, value_template: str, expected: str) -> None: """Test the base64_encode filter.""" - assert template.Template(value_template, hass).async_render() == expected + assert render(hass, value_template) == expected def test_base64_decode(hass: HomeAssistant) -> None: """Test the base64_decode filter.""" assert ( - template.Template( - '{{ "aG9tZWFzc2lzdGFudA==" | base64_decode }}', hass - ).async_render() - == "homeassistant" + render(hass, '{{ "aG9tZWFzc2lzdGFudA==" | base64_decode }}') == "homeassistant" ) assert ( - template.Template( - '{{ "aG9tZWFzc2lzdGFudA==" | base64_decode(None) }}', hass - ).async_render() + render(hass, '{{ "aG9tZWFzc2lzdGFudA==" | base64_decode(None) }}') == b"homeassistant" ) assert ( - template.Template( - '{{ "aG9tZWFzc2lzdGFudA==" | base64_decode("ascii") }}', hass - ).async_render() + render(hass, '{{ "aG9tZWFzc2lzdGFudA==" | base64_decode("ascii") }}') == "homeassistant" ) diff --git a/tests/helpers/template/extensions/test_collection.py b/tests/helpers/template/extensions/test_collection.py index 88cdb00dd19..d392d43c635 100644 --- a/tests/helpers/template/extensions/test_collection.py +++ b/tests/helpers/template/extensions/test_collection.py @@ -8,7 +8,8 @@ import pytest from homeassistant.core import HomeAssistant from homeassistant.exceptions import TemplateError -from homeassistant.helpers import template + +from tests.helpers.template.helpers import render @pytest.mark.parametrize( @@ -27,10 +28,7 @@ from homeassistant.helpers import template ) def test_is_list(hass: HomeAssistant, value: Any, expected: bool) -> None: """Test list test.""" - assert ( - template.Template("{{ value is list }}", hass).async_render({"value": value}) - == expected - ) + assert render(hass, "{{ value is list }}", {"value": value}) == expected @pytest.mark.parametrize( @@ -49,10 +47,7 @@ def test_is_list(hass: HomeAssistant, value: Any, expected: bool) -> None: ) def test_is_set(hass: HomeAssistant, value: Any, expected: bool) -> None: """Test set test.""" - assert ( - template.Template("{{ value is set }}", hass).async_render({"value": value}) - == expected - ) + assert render(hass, "{{ value is set }}", {"value": value}) == expected @pytest.mark.parametrize( @@ -71,10 +66,7 @@ def test_is_set(hass: HomeAssistant, value: Any, expected: bool) -> None: ) def test_is_tuple(hass: HomeAssistant, value: Any, expected: bool) -> None: """Test tuple test.""" - assert ( - template.Template("{{ value is tuple }}", hass).async_render({"value": value}) - == expected - ) + assert render(hass, "{{ value is tuple }}", {"value": value}) == expected @pytest.mark.parametrize( @@ -93,8 +85,7 @@ def test_is_tuple(hass: HomeAssistant, value: Any, expected: bool) -> None: def test_set(hass: HomeAssistant, value: Any, expected: bool) -> None: """Test set conversion.""" assert ( - template.Template("{{ set(value) }}", hass).async_render({"value": value}) - == list(expected.values())[0] + render(hass, "{{ set(value) }}", {"value": value}) == list(expected.values())[0] ) @@ -113,9 +104,7 @@ def test_set(hass: HomeAssistant, value: Any, expected: bool) -> None: ) def test_tuple(hass: HomeAssistant, value: Any, expected: bool) -> None: """Test tuple conversion.""" - result = template.Template("{{ tuple(value) }}", hass).async_render( - {"value": value} - ) + result = render(hass, "{{ tuple(value) }}", {"value": value}) expected_value = list(expected.values())[0] if isinstance(value, set): # Sets don't have predictable order assert set(result) == set(expected_value) @@ -133,18 +122,11 @@ def test_tuple(hass: HomeAssistant, value: Any, expected: bool) -> None: ) def test_zip(hass: HomeAssistant, cola, colb, expected) -> None: """Test zip.""" - assert ( - template.Template("{{ zip(cola, colb) | list }}", hass).async_render( - {"cola": cola, "colb": colb} - ) - == expected - ) - assert ( - template.Template( - "[{% for a, b in zip(cola, colb) %}({{a}}, {{b}}), {% endfor %}]", hass - ).async_render({"cola": cola, "colb": colb}) - == expected - ) + for tpl in ( + "{{ zip(cola, colb) | list }}", + "[{% for a, b in zip(cola, colb) %}({{a}}, {{b}}), {% endfor %}]", + ): + assert render(hass, tpl, {"cola": cola, "colb": colb}) == expected @pytest.mark.parametrize( @@ -156,38 +138,27 @@ def test_zip(hass: HomeAssistant, cola, colb, expected) -> None: ) def test_unzip(hass: HomeAssistant, col, expected) -> None: """Test unzipping using zip.""" - assert ( - template.Template("{{ zip(*col) | list }}", hass).async_render({"col": col}) - == expected - ) - assert ( - template.Template( - "{% set a, b = zip(*col) %}[{{a}}, {{b}}]", hass - ).async_render({"col": col}) - == expected - ) + for tpl in ( + "{{ zip(*col) | list }}", + "{% set a, b = zip(*col) %}[{{a}}, {{b}}]", + ): + assert render(hass, tpl, {"col": col}) == expected def test_shuffle(hass: HomeAssistant) -> None: """Test shuffle.""" # Test basic shuffle - result = template.Template("{{ shuffle([1, 2, 3, 4, 5]) }}", hass).async_render() + result = render(hass, "{{ shuffle([1, 2, 3, 4, 5]) }}") assert len(result) == 5 assert set(result) == {1, 2, 3, 4, 5} # Test shuffle with seed - result1 = template.Template( - "{{ shuffle([1, 2, 3, 4, 5], seed=42) }}", hass - ).async_render() - result2 = template.Template( - "{{ shuffle([1, 2, 3, 4, 5], seed=42) }}", hass - ).async_render() + result1 = render(hass, "{{ shuffle([1, 2, 3, 4, 5], seed=42) }}") + result2 = render(hass, "{{ shuffle([1, 2, 3, 4, 5], seed=42) }}") assert result1 == result2 # Same seed should give same result # Test shuffle with different seed - result3 = template.Template( - "{{ shuffle([1, 2, 3, 4, 5], seed=123) }}", hass - ).async_render() + result3 = render(hass, "{{ shuffle([1, 2, 3, 4, 5], seed=123) }}") # Different seeds should usually give different results # (but we can't guarantee it for small lists) assert len(result3) == 5 @@ -197,144 +168,115 @@ def test_shuffle(hass: HomeAssistant) -> None: def test_flatten(hass: HomeAssistant) -> None: """Test flatten.""" # Test basic flattening - assert template.Template( - "{{ flatten([[1, 2], [3, 4]]) }}", hass - ).async_render() == [1, 2, 3, 4] + assert render(hass, "{{ flatten([[1, 2], [3, 4]]) }}") == [1, 2, 3, 4] # Test nested flattening - assert template.Template( - "{{ flatten([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) }}", hass - ).async_render() == [1, 2, 3, 4, 5, 6, 7, 8] + expected = [1, 2, 3, 4, 5, 6, 7, 8] + assert ( + render(hass, "{{ flatten([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) }}") == expected + ) # Test flattening with levels - assert template.Template( - "{{ flatten([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], levels=1) }}", hass - ).async_render() == [[1, 2], [3, 4], [5, 6], [7, 8]] + assert render( + hass, "{{ flatten([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], levels=1) }}" + ) == [[1, 2], [3, 4], [5, 6], [7, 8]] # Test mixed types - assert template.Template( - "{{ flatten([[1, 'a'], [2, 'b']]) }}", hass - ).async_render() == [1, "a", 2, "b"] + assert render(hass, "{{ flatten([[1, 'a'], [2, 'b']]) }}") == [1, "a", 2, "b"] # Test empty list - assert template.Template("{{ flatten([]) }}", hass).async_render() == [] + assert render(hass, "{{ flatten([]) }}") == [] # Test single level - assert template.Template("{{ flatten([1, 2, 3]) }}", hass).async_render() == [ - 1, - 2, - 3, - ] + assert render(hass, "{{ flatten([1, 2, 3]) }}") == [1, 2, 3] def test_intersect(hass: HomeAssistant) -> None: """Test intersect.""" # Test basic intersection - result = template.Template( - "{{ [1, 2, 3, 4] | intersect([3, 4, 5, 6]) | sort }}", hass - ).async_render() + result = render(hass, "{{ [1, 2, 3, 4] | intersect([3, 4, 5, 6]) | sort }}") assert result == [3, 4] # Test no intersection - result = template.Template("{{ [1, 2] | intersect([3, 4]) }}", hass).async_render() + result = render(hass, "{{ [1, 2] | intersect([3, 4]) }}") assert result == [] # Test string intersection - result = template.Template( - "{{ ['a', 'b', 'c'] | intersect(['b', 'c', 'd']) | sort }}", hass - ).async_render() + result = render(hass, "{{ ['a', 'b', 'c'] | intersect(['b', 'c', 'd']) | sort }}") assert result == ["b", "c"] # Test empty list intersection - result = template.Template("{{ [] | intersect([1, 2, 3]) }}", hass).async_render() + result = render(hass, "{{ [] | intersect([1, 2, 3]) }}") assert result == [] def test_difference(hass: HomeAssistant) -> None: """Test difference.""" # Test basic difference - result = template.Template( - "{{ [1, 2, 3, 4] | difference([3, 4, 5, 6]) | sort }}", hass - ).async_render() + result = render(hass, "{{ [1, 2, 3, 4] | difference([3, 4, 5, 6]) | sort }}") assert result == [1, 2] # Test no difference - result = template.Template( - "{{ [1, 2] | difference([1, 2, 3, 4]) }}", hass - ).async_render() + result = render(hass, "{{ [1, 2] | difference([1, 2, 3, 4]) }}") assert result == [] # Test string difference - result = template.Template( - "{{ ['a', 'b', 'c'] | difference(['b', 'c', 'd']) | sort }}", hass - ).async_render() + result = render(hass, "{{ ['a', 'b', 'c'] | difference(['b', 'c', 'd']) | sort }}") assert result == ["a"] # Test empty list difference - result = template.Template("{{ [] | difference([1, 2, 3]) }}", hass).async_render() + result = render(hass, "{{ [] | difference([1, 2, 3]) }}") assert result == [] def test_union(hass: HomeAssistant) -> None: """Test union.""" # Test basic union - result = template.Template( - "{{ [1, 2, 3] | union([3, 4, 5]) | sort }}", hass - ).async_render() + result = render(hass, "{{ [1, 2, 3] | union([3, 4, 5]) | sort }}") assert result == [1, 2, 3, 4, 5] # Test string union - result = template.Template( - "{{ ['a', 'b'] | union(['b', 'c']) | sort }}", hass - ).async_render() + result = render(hass, "{{ ['a', 'b'] | union(['b', 'c']) | sort }}") assert result == ["a", "b", "c"] # Test empty list union - result = template.Template( - "{{ [] | union([1, 2, 3]) | sort }}", hass - ).async_render() + result = render(hass, "{{ [] | union([1, 2, 3]) | sort }}") assert result == [1, 2, 3] # Test duplicate elements - result = template.Template( - "{{ [1, 1, 2, 2] | union([2, 2, 3, 3]) | sort }}", hass - ).async_render() + result = render(hass, "{{ [1, 1, 2, 2] | union([2, 2, 3, 3]) | sort }}") assert result == [1, 2, 3] def test_symmetric_difference(hass: HomeAssistant) -> None: """Test symmetric_difference.""" # Test basic symmetric difference - result = template.Template( - "{{ [1, 2, 3, 4] | symmetric_difference([3, 4, 5, 6]) | sort }}", hass - ).async_render() + result = render( + hass, "{{ [1, 2, 3, 4] | symmetric_difference([3, 4, 5, 6]) | sort }}" + ) assert result == [1, 2, 5, 6] # Test no symmetric difference (identical sets) - result = template.Template( - "{{ [1, 2, 3] | symmetric_difference([1, 2, 3]) }}", hass - ).async_render() + result = render(hass, "{{ [1, 2, 3] | symmetric_difference([1, 2, 3]) }}") assert result == [] # Test string symmetric difference - result = template.Template( - "{{ ['a', 'b', 'c'] | symmetric_difference(['b', 'c', 'd']) | sort }}", hass - ).async_render() + result = render( + hass, "{{ ['a', 'b', 'c'] | symmetric_difference(['b', 'c', 'd']) | sort }}" + ) assert result == ["a", "d"] # Test empty list symmetric difference - result = template.Template( - "{{ [] | symmetric_difference([1, 2, 3]) | sort }}", hass - ).async_render() + result = render(hass, "{{ [] | symmetric_difference([1, 2, 3]) | sort }}") assert result == [1, 2, 3] def test_collection_functions_as_tests(hass: HomeAssistant) -> None: """Test that type checking functions work as tests.""" # Test various type checking functions - assert template.Template("{{ [1,2,3] is list }}", hass).async_render() - assert template.Template("{{ set([1,2,3]) is set }}", hass).async_render() - assert template.Template("{{ (1,2,3) is tuple }}", hass).async_render() + assert render(hass, "{{ [1,2,3] is list }}") + assert render(hass, "{{ set([1,2,3]) is set }}") + assert render(hass, "{{ (1,2,3) is tuple }}") def test_collection_error_handling(hass: HomeAssistant) -> None: @@ -342,16 +284,16 @@ def test_collection_error_handling(hass: HomeAssistant) -> None: # Test flatten with non-iterable with pytest.raises(TemplateError, match="flatten expected a list"): - template.Template("{{ flatten(123) }}", hass).async_render() + render(hass, "{{ flatten(123) }}") # Test intersect with non-iterable with pytest.raises(TemplateError, match="intersect expected a list"): - template.Template("{{ [1, 2] | intersect(123) }}", hass).async_render() + render(hass, "{{ [1, 2] | intersect(123) }}") # Test difference with non-iterable with pytest.raises(TemplateError, match="difference expected a list"): - template.Template("{{ [1, 2] | difference(123) }}", hass).async_render() + render(hass, "{{ [1, 2] | difference(123) }}") # Test shuffle with no arguments with pytest.raises(TemplateError, match="shuffle expected at least 1 argument"): - template.Template("{{ shuffle() }}", hass).async_render() + render(hass, "{{ shuffle() }}") diff --git a/tests/helpers/template/extensions/test_crypto.py b/tests/helpers/template/extensions/test_crypto.py index f1e4c3b39cc..7fe316c497d 100644 --- a/tests/helpers/template/extensions/test_crypto.py +++ b/tests/helpers/template/extensions/test_crypto.py @@ -3,56 +3,33 @@ from __future__ import annotations from homeassistant.core import HomeAssistant -from homeassistant.helpers import template + +from tests.helpers.template.helpers import render def test_md5(hass: HomeAssistant) -> None: """Test the md5 function and filter.""" - assert ( - template.Template("{{ md5('Home Assistant') }}", hass).async_render() - == "3d15e5c102c3413d0337393c3287e006" - ) - - assert ( - template.Template("{{ 'Home Assistant' | md5 }}", hass).async_render() - == "3d15e5c102c3413d0337393c3287e006" - ) + ha_md5 = "3d15e5c102c3413d0337393c3287e006" + assert render(hass, "{{ md5('Home Assistant') }}") == ha_md5 + assert render(hass, "{{ 'Home Assistant' | md5 }}") == ha_md5 def test_sha1(hass: HomeAssistant) -> None: """Test the sha1 function and filter.""" - assert ( - template.Template("{{ sha1('Home Assistant') }}", hass).async_render() - == "c8fd3bb19b94312664faa619af7729bdbf6e9f8a" - ) - - assert ( - template.Template("{{ 'Home Assistant' | sha1 }}", hass).async_render() - == "c8fd3bb19b94312664faa619af7729bdbf6e9f8a" - ) + ha_sha1 = "c8fd3bb19b94312664faa619af7729bdbf6e9f8a" + assert render(hass, "{{ sha1('Home Assistant') }}") == ha_sha1 + assert render(hass, "{{ 'Home Assistant' | sha1 }}") == ha_sha1 def test_sha256(hass: HomeAssistant) -> None: """Test the sha256 function and filter.""" - assert ( - template.Template("{{ sha256('Home Assistant') }}", hass).async_render() - == "2a366abb0cd47f51f3725bf0fb7ebcb4fefa6e20f4971e25fe2bb8da8145ce2b" - ) - - assert ( - template.Template("{{ 'Home Assistant' | sha256 }}", hass).async_render() - == "2a366abb0cd47f51f3725bf0fb7ebcb4fefa6e20f4971e25fe2bb8da8145ce2b" - ) + ha_sha256 = "2a366abb0cd47f51f3725bf0fb7ebcb4fefa6e20f4971e25fe2bb8da8145ce2b" + assert render(hass, "{{ sha256('Home Assistant') }}") == ha_sha256 + assert render(hass, "{{ 'Home Assistant' | sha256 }}") == ha_sha256 def test_sha512(hass: HomeAssistant) -> None: """Test the sha512 function and filter.""" - assert ( - template.Template("{{ sha512('Home Assistant') }}", hass).async_render() - == "9e3c2cdd1fbab0037378d37e1baf8a3a4bf92c54b56ad1d459deee30ccbb2acbebd7a3614552ea08992ad27dedeb7b4c5473525ba90cb73dbe8b9ec5f69295bb" - ) - - assert ( - template.Template("{{ 'Home Assistant' | sha512 }}", hass).async_render() - == "9e3c2cdd1fbab0037378d37e1baf8a3a4bf92c54b56ad1d459deee30ccbb2acbebd7a3614552ea08992ad27dedeb7b4c5473525ba90cb73dbe8b9ec5f69295bb" - ) + ha_sha512 = "9e3c2cdd1fbab0037378d37e1baf8a3a4bf92c54b56ad1d459deee30ccbb2acbebd7a3614552ea08992ad27dedeb7b4c5473525ba90cb73dbe8b9ec5f69295bb" + assert render(hass, "{{ sha512('Home Assistant') }}") == ha_sha512 + assert render(hass, "{{ 'Home Assistant' | sha512 }}") == ha_sha512 diff --git a/tests/helpers/template/extensions/test_math.py b/tests/helpers/template/extensions/test_math.py index 4cf26cdf517..0ee79f4e0a3 100644 --- a/tests/helpers/template/extensions/test_math.py +++ b/tests/helpers/template/extensions/test_math.py @@ -8,7 +8,6 @@ import pytest from homeassistant.core import HomeAssistant from homeassistant.exceptions import TemplateError -from homeassistant.helpers import template from tests.helpers.template.helpers import render @@ -29,29 +28,19 @@ def test_logarithm(hass: HomeAssistant) -> None: ] for value, base, expected in tests: - assert ( - template.Template( - f"{{{{ {value} | log({base}) | round(1) }}}}", hass - ).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | log({base}) | round(1) }}}}") == expected - assert ( - template.Template( - f"{{{{ log({value}, {base}) | round(1) }}}}", hass - ).async_render() - == expected - ) + assert render(hass, f"{{{{ log({value}, {base}) | round(1) }}}}") == expected # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ invalid | log(_) }}", hass).async_render() + render(hass, "{{ invalid | log(_) }}") with pytest.raises(TemplateError): - template.Template("{{ log(invalid, _) }}", hass).async_render() + render(hass, "{{ log(invalid, _) }}") with pytest.raises(TemplateError): - template.Template("{{ 10 | log(invalid) }}", hass).async_render() + render(hass, "{{ 10 | log(invalid) }}") with pytest.raises(TemplateError): - template.Template("{{ log(10, invalid) }}", hass).async_render() + render(hass, "{{ log(10, invalid) }}") # Test handling of default return value assert render(hass, "{{ 'no_number' | log(10, 1) }}") == 1 @@ -73,19 +62,14 @@ def test_sine(hass: HomeAssistant) -> None: ] for value, expected in tests: - assert ( - template.Template( - f"{{{{ {value} | sin | round(3) }}}}", hass - ).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | sin | round(3) }}}}") == expected assert render(hass, f"{{{{ sin({value}) | round(3) }}}}") == expected # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ 'duck' | sin }}", hass).async_render() + render(hass, "{{ 'duck' | sin }}") with pytest.raises(TemplateError): - template.Template("{{ invalid | sin('duck') }}", hass).async_render() + render(hass, "{{ invalid | sin('duck') }}") # Test handling of default return value assert render(hass, "{{ 'no_number' | sin(1) }}") == 1 @@ -105,17 +89,12 @@ def test_cosine(hass: HomeAssistant) -> None: ] for value, expected in tests: - assert ( - template.Template( - f"{{{{ {value} | cos | round(3) }}}}", hass - ).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | cos | round(3) }}}}") == expected assert render(hass, f"{{{{ cos({value}) | round(3) }}}}") == expected # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ 'duck' | cos }}", hass).async_render() + render(hass, "{{ 'duck' | cos }}") # Test handling of default return value assert render(hass, "{{ 'no_number' | cos(1) }}") == 1 @@ -134,17 +113,12 @@ def test_tangent(hass: HomeAssistant) -> None: ] for value, expected in tests: - assert ( - template.Template( - f"{{{{ {value} | tan | round(3) }}}}", hass - ).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | tan | round(3) }}}}") == expected assert render(hass, f"{{{{ tan({value}) | round(3) }}}}") == expected # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ 'duck' | tan }}", hass).async_render() + render(hass, "{{ 'duck' | tan }}") # Test handling of default return value assert render(hass, "{{ 'no_number' | tan(1) }}") == 1 @@ -165,17 +139,14 @@ def test_square_root(hass: HomeAssistant) -> None: ] for value, expected in tests: - assert ( - template.Template(f"{{{{ {value} | sqrt }}}}", hass).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | sqrt }}}}") == expected assert render(hass, f"{{{{ sqrt({value}) }}}}") == expected # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ 'duck' | sqrt }}", hass).async_render() + render(hass, "{{ 'duck' | sqrt }}") with pytest.raises(TemplateError): - template.Template("{{ -1 | sqrt }}", hass).async_render() + render(hass, "{{ -1 | sqrt }}") # Test handling of default return value assert render(hass, "{{ 'no_number' | sqrt(1) }}") == 1 @@ -217,114 +188,95 @@ def test_arc_functions(hass: HomeAssistant) -> None: def test_average(hass: HomeAssistant) -> None: """Test the average function.""" - assert template.Template("{{ average([1, 2, 3]) }}", hass).async_render() == 2 - assert template.Template("{{ average(1, 2, 3) }}", hass).async_render() == 2 + assert render(hass, "{{ average([1, 2, 3]) }}") == 2 + assert render(hass, "{{ average(1, 2, 3) }}") == 2 # Testing of default values - assert template.Template("{{ average([1, 2, 3], -1) }}", hass).async_render() == 2 - assert template.Template("{{ average([], -1) }}", hass).async_render() == -1 - assert template.Template("{{ average([], default=-1) }}", hass).async_render() == -1 - assert ( - template.Template("{{ average([], 5, default=-1) }}", hass).async_render() == -1 - ) - assert ( - template.Template("{{ average(1, 'a', 3, default=-1) }}", hass).async_render() - == -1 - ) + assert render(hass, "{{ average([1, 2, 3], -1) }}") == 2 + assert render(hass, "{{ average([], -1) }}") == -1 + assert render(hass, "{{ average([], default=-1) }}") == -1 + assert render(hass, "{{ average([], 5, default=-1) }}") == -1 + assert render(hass, "{{ average(1, 'a', 3, default=-1) }}") == -1 with pytest.raises(TemplateError): - template.Template("{{ average() }}", hass).async_render() + render(hass, "{{ average() }}") with pytest.raises(TemplateError): - template.Template("{{ average([]) }}", hass).async_render() + render(hass, "{{ average([]) }}") def test_median(hass: HomeAssistant) -> None: """Test the median function.""" - assert template.Template("{{ median([1, 2, 3]) }}", hass).async_render() == 2 - assert template.Template("{{ median([1, 2, 3, 4]) }}", hass).async_render() == 2.5 - assert template.Template("{{ median(1, 2, 3) }}", hass).async_render() == 2 + assert render(hass, "{{ median([1, 2, 3]) }}") == 2 + assert render(hass, "{{ median([1, 2, 3, 4]) }}") == 2.5 + assert render(hass, "{{ median(1, 2, 3) }}") == 2 # Testing of default values - assert template.Template("{{ median([1, 2, 3], -1) }}", hass).async_render() == 2 - assert template.Template("{{ median([], -1) }}", hass).async_render() == -1 - assert template.Template("{{ median([], default=-1) }}", hass).async_render() == -1 + assert render(hass, "{{ median([1, 2, 3], -1) }}") == 2 + assert render(hass, "{{ median([], -1) }}") == -1 + assert render(hass, "{{ median([], default=-1) }}") == -1 with pytest.raises(TemplateError): - template.Template("{{ median() }}", hass).async_render() + render(hass, "{{ median() }}") with pytest.raises(TemplateError): - template.Template("{{ median([]) }}", hass).async_render() + render(hass, "{{ median([]) }}") def test_statistical_mode(hass: HomeAssistant) -> None: """Test the statistical mode function.""" - assert ( - template.Template("{{ statistical_mode([1, 1, 2, 3]) }}", hass).async_render() - == 1 - ) - assert ( - template.Template("{{ statistical_mode(1, 1, 2, 3) }}", hass).async_render() - == 1 - ) + assert render(hass, "{{ statistical_mode([1, 1, 2, 3]) }}") == 1 + assert render(hass, "{{ statistical_mode(1, 1, 2, 3) }}") == 1 # Testing of default values - assert ( - template.Template("{{ statistical_mode([1, 1, 2], -1) }}", hass).async_render() - == 1 - ) - assert ( - template.Template("{{ statistical_mode([], -1) }}", hass).async_render() == -1 - ) - assert ( - template.Template("{{ statistical_mode([], default=-1) }}", hass).async_render() - == -1 - ) + assert render(hass, "{{ statistical_mode([1, 1, 2], -1) }}") == 1 + assert render(hass, "{{ statistical_mode([], -1) }}") == -1 + assert render(hass, "{{ statistical_mode([], default=-1) }}") == -1 with pytest.raises(TemplateError): - template.Template("{{ statistical_mode() }}", hass).async_render() + render(hass, "{{ statistical_mode() }}") with pytest.raises(TemplateError): - template.Template("{{ statistical_mode([]) }}", hass).async_render() + render(hass, "{{ statistical_mode([]) }}") def test_min_max_functions(hass: HomeAssistant) -> None: """Test min and max functions.""" # Test min function - assert template.Template("{{ min([1, 2, 3]) }}", hass).async_render() == 1 - assert template.Template("{{ min(1, 2, 3) }}", hass).async_render() == 1 + assert render(hass, "{{ min([1, 2, 3]) }}") == 1 + assert render(hass, "{{ min(1, 2, 3) }}") == 1 # Test max function - assert template.Template("{{ max([1, 2, 3]) }}", hass).async_render() == 3 - assert template.Template("{{ max(1, 2, 3) }}", hass).async_render() == 3 + assert render(hass, "{{ max([1, 2, 3]) }}") == 3 + assert render(hass, "{{ max(1, 2, 3) }}") == 3 # Test error handling with pytest.raises(TemplateError): - template.Template("{{ min() }}", hass).async_render() + render(hass, "{{ min() }}") with pytest.raises(TemplateError): - template.Template("{{ max() }}", hass).async_render() + render(hass, "{{ max() }}") def test_bitwise_and(hass: HomeAssistant) -> None: """Test bitwise and.""" - assert template.Template("{{ bitwise_and(8, 2) }}", hass).async_render() == 0 - assert template.Template("{{ bitwise_and(10, 2) }}", hass).async_render() == 2 - assert template.Template("{{ bitwise_and(8, 8) }}", hass).async_render() == 8 + assert render(hass, "{{ bitwise_and(8, 2) }}") == 0 + assert render(hass, "{{ bitwise_and(10, 2) }}") == 2 + assert render(hass, "{{ bitwise_and(8, 8) }}") == 8 def test_bitwise_or(hass: HomeAssistant) -> None: """Test bitwise or.""" - assert template.Template("{{ bitwise_or(8, 2) }}", hass).async_render() == 10 - assert template.Template("{{ bitwise_or(8, 8) }}", hass).async_render() == 8 - assert template.Template("{{ bitwise_or(10, 2) }}", hass).async_render() == 10 + assert render(hass, "{{ bitwise_or(8, 2) }}") == 10 + assert render(hass, "{{ bitwise_or(8, 8) }}") == 8 + assert render(hass, "{{ bitwise_or(10, 2) }}") == 10 def test_bitwise_xor(hass: HomeAssistant) -> None: """Test bitwise xor.""" - assert template.Template("{{ bitwise_xor(8, 2) }}", hass).async_render() == 10 - assert template.Template("{{ bitwise_xor(8, 8) }}", hass).async_render() == 0 - assert template.Template("{{ bitwise_xor(10, 2) }}", hass).async_render() == 8 + assert render(hass, "{{ bitwise_xor(8, 2) }}") == 10 + assert render(hass, "{{ bitwise_xor(8, 8) }}") == 0 + assert render(hass, "{{ bitwise_xor(10, 2) }}") == 8 @pytest.mark.parametrize( @@ -361,30 +313,30 @@ def test_min_max_attribute(hass: HomeAssistant, attribute) -> None: }, ) assert ( - template.Template( + render( + hass, f"{{{{ (state_attr('test.object', 'objects') | min(attribute='{attribute}'))['{attribute}']}}}}", - hass, - ).async_render() + ) == 1 ) assert ( - template.Template( + render( + hass, f"{{{{ (min(state_attr('test.object', 'objects'), attribute='{attribute}'))['{attribute}']}}}}", - hass, - ).async_render() + ) == 1 ) assert ( - template.Template( - f"{{{{ (state_attr('test.object', 'objects') | max(attribute='{attribute}'))['{attribute}']}}}}", + render( hass, - ).async_render() + f"{{{{ (state_attr('test.object', 'objects') | max(attribute='{attribute}'))['{attribute}']}}}}", + ) == 3 ) assert ( - template.Template( - f"{{{{ (max(state_attr('test.object', 'objects'), attribute='{attribute}'))['{attribute}']}}}}", + render( hass, - ).async_render() + f"{{{{ (max(state_attr('test.object', 'objects'), attribute='{attribute}'))['{attribute}']}}}}", + ) == 3 ) diff --git a/tests/helpers/template/extensions/test_regex.py b/tests/helpers/template/extensions/test_regex.py index 290b55bad1f..1c508bfb3b1 100644 --- a/tests/helpers/template/extensions/test_regex.py +++ b/tests/helpers/template/extensions/test_regex.py @@ -6,260 +6,169 @@ import pytest from homeassistant.core import HomeAssistant from homeassistant.exceptions import TemplateError -from homeassistant.helpers import template + +from tests.helpers.template.helpers import render def test_regex_match(hass: HomeAssistant) -> None: """Test regex_match method.""" - tpl = template.Template( - r""" -{{ '123-456-7890' | regex_match('(\\d{3})-(\\d{3})-(\\d{4})') }} - """, - hass, - ) - assert tpl.async_render() is True - tpl = template.Template( - """ -{{ 'Home Assistant test' | regex_match('home', True) }} - """, - hass, + result = render( + hass, r"""{{ '123-456-7890' | regex_match('(\\d{3})-(\\d{3})-(\\d{4})') }}""" ) - assert tpl.async_render() is True + assert result is True - tpl = template.Template( - """ - {{ 'Another Home Assistant test' | regex_match('Home') }} - """, - hass, - ) - assert tpl.async_render() is False + result = render(hass, """{{ 'Home Assistant test' | regex_match('home', True) }}""") + assert result is True - tpl = template.Template( - """ -{{ ['Home Assistant test'] | regex_match('.*Assist') }} - """, - hass, - ) - assert tpl.async_render() is True + result = render(hass, """{{ 'Another Home Assistant test'|regex_match('Home') }}""") + assert result is False + + result = render(hass, """{{ ['Home Assistant test'] | regex_match('.*Assist') }}""") + assert result is True def test_match_test(hass: HomeAssistant) -> None: """Test match test.""" - tpl = template.Template( - r""" -{{ '123-456-7890' is match('(\\d{3})-(\\d{3})-(\\d{4})') }} - """, - hass, + + result = render( + hass, r"""{{ '123-456-7890' is match('(\\d{3})-(\\d{3})-(\\d{4})') }}""" ) - assert tpl.async_render() is True + assert result is True def test_regex_search(hass: HomeAssistant) -> None: """Test regex_search method.""" - tpl = template.Template( - r""" -{{ '123-456-7890' | regex_search('(\\d{3})-(\\d{3})-(\\d{4})') }} - """, - hass, - ) - assert tpl.async_render() is True - tpl = template.Template( - """ -{{ 'Home Assistant test' | regex_search('home', True) }} - """, - hass, + result = render( + hass, r"""{{ '123-456-7890' | regex_search('(\\d{3})-(\\d{3})-(\\d{4})') }}""" ) - assert tpl.async_render() is True + assert result is True - tpl = template.Template( - """ - {{ 'Another Home Assistant test' | regex_search('Home') }} - """, - hass, + result = render( + hass, """{{ 'Home Assistant test' | regex_search('home', True) }}""" ) - assert tpl.async_render() is True + assert result is True - tpl = template.Template( - """ -{{ ['Home Assistant test'] | regex_search('Assist') }} - """, - hass, + result = render( + hass, """ {{ 'Another Home Assistant test' | regex_search('Home') }}""" ) - assert tpl.async_render() is True + assert result is True + + result = render(hass, """{{ ['Home Assistant test'] | regex_search('Assist') }}""") + assert result is True def test_search_test(hass: HomeAssistant) -> None: """Test search test.""" - tpl = template.Template( - r""" -{{ '123-456-7890' is search('(\\d{3})-(\\d{3})-(\\d{4})') }} - """, - hass, + + result = render( + hass, r"""{{ '123-456-7890' is search('(\\d{3})-(\\d{3})-(\\d{4})') }}""" ) - assert tpl.async_render() is True + assert result is True def test_regex_replace(hass: HomeAssistant) -> None: """Test regex_replace method.""" - tpl = template.Template( - r""" -{{ 'Hello World' | regex_replace('(Hello\\s)',) }} - """, - hass, - ) - assert tpl.async_render() == "World" - tpl = template.Template( - """ -{{ ['Home hinderant test'] | regex_replace('hinder', 'Assist') }} - """, - hass, + result = render(hass, r"""{{ 'Hello World' | regex_replace('(Hello\\s)',) }}""") + assert result == "World" + + result = render( + hass, """{{ ['Home hinderant test'] | regex_replace('hinder', 'Assist') }}""" ) - assert tpl.async_render() == ["Home Assistant test"] + assert result == ["Home Assistant test"] def test_regex_findall(hass: HomeAssistant) -> None: """Test regex_findall method.""" - tpl = template.Template( - """ -{{ 'Flight from JFK to LHR' | regex_findall('([A-Z]{3})') }} - """, - hass, + + result = render( + hass, """{{ 'Flight from JFK to LHR' | regex_findall('([A-Z]{3})') }}""" ) - assert tpl.async_render() == ["JFK", "LHR"] + assert result == ["JFK", "LHR"] def test_regex_findall_index(hass: HomeAssistant) -> None: """Test regex_findall_index method.""" - tpl = template.Template( - """ -{{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 0) }} - """, - hass, - ) - assert tpl.async_render() == "JFK" - tpl = template.Template( - """ -{{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 1) }} - """, + result = render( hass, + """{{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 0) }}""", ) - assert tpl.async_render() == "LHR" + assert result == "JFK" + + result = render( + hass, + """{{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 1) }}""", + ) + assert result == "LHR" def test_regex_ignorecase_parameter(hass: HomeAssistant) -> None: """Test ignorecase parameter across all regex functions.""" # Test regex_match with ignorecase - tpl = template.Template( - """ -{{ 'TEST' | regex_match('test', True) }} - """, - hass, - ) - assert tpl.async_render() is True + + result = render(hass, """{{ 'TEST' | regex_match('test', True) }}""") + assert result is True # Test regex_search with ignorecase - tpl = template.Template( - """ -{{ 'TEST STRING' | regex_search('test', True) }} - """, - hass, - ) - assert tpl.async_render() is True + + result = render(hass, """{{ 'TEST STRING' | regex_search('test', True) }}""") + assert result is True # Test regex_replace with ignorecase - tpl = template.Template( - """ -{{ 'TEST' | regex_replace('test', 'replaced', True) }} - """, - hass, - ) - assert tpl.async_render() == "replaced" + + result = render(hass, """{{ 'TEST' | regex_replace('test', 'replaced', True) }}""") + assert result == "replaced" # Test regex_findall with ignorecase - tpl = template.Template( - """ -{{ 'TEST test Test' | regex_findall('test', True) }} - """, - hass, - ) - assert tpl.async_render() == ["TEST", "test", "Test"] + + result = render(hass, """{{ 'TEST test Test' | regex_findall('test', True) }}""") + assert result == ["TEST", "test", "Test"] def test_regex_with_non_string_input(hass: HomeAssistant) -> None: """Test regex functions with non-string input (automatic conversion).""" # Test with integer - tpl = template.Template( - r""" -{{ 12345 | regex_match('\\d+') }} - """, - hass, - ) - assert tpl.async_render() is True + + result = render(hass, r"""{{ 12345 | regex_match('\\d+') }}""") + assert result is True # Test with list (string conversion) - tpl = template.Template( - r""" -{{ [1, 2, 3] | regex_search('\\d') }} - """, - hass, - ) - assert tpl.async_render() is True + + result = render(hass, r"""{{ [1, 2, 3] | regex_search('\\d') }}""") + assert result is True def test_regex_edge_cases(hass: HomeAssistant) -> None: """Test regex functions with edge cases.""" # Test with empty string - tpl = template.Template( - """ -{{ '' | regex_match('.*') }} - """, - hass, - ) - assert tpl.async_render() is True + + assert render(hass, """{{ '' | regex_match('.*') }}""") is True # Test regex_findall_index with out of bounds index - tpl = template.Template( - """ -{{ 'test' | regex_findall_index('t', 5) }} - """, - hass, - ) with pytest.raises(TemplateError): - tpl.async_render() + render(hass, """{{ 'test' | regex_findall_index('t', 5) }}""") # Test with invalid regex pattern - tpl = template.Template( - """ -{{ 'test' | regex_match('[') }} - """, - hass, - ) with pytest.raises(TemplateError): # re.error wrapped in TemplateError - tpl.async_render() + render(hass, """{{ 'test' | regex_match('[') }}""") def test_regex_groups_and_replacement_patterns(hass: HomeAssistant) -> None: """Test regex with groups and replacement patterns.""" # Test replacement with groups - tpl = template.Template( - r""" -{{ 'John Doe' | regex_replace('(\\w+) (\\w+)', '\\2, \\1') }} - """, - hass, + + result = render( + hass, r"""{{ 'John Doe' | regex_replace('(\\w+) (\\w+)', '\\2, \\1') }}""" ) - assert tpl.async_render() == "Doe, John" + assert result == "Doe, John" # Test findall with groups - tpl = template.Template( - r""" -{{ 'Email: test@example.com, Phone: 123-456-7890' | regex_findall('(\\w+@\\w+\\.\\w+)|(\\d{3}-\\d{3}-\\d{4})') }} - """, + result = render( hass, + r"""{{ 'Email: test@example.com, Phone: 123-456-7890' | regex_findall('(\\w+@\\w+\\.\\w+)|(\\d{3}-\\d{3}-\\d{4})') }}""", ) - result = tpl.async_render() # The result will contain tuples with empty strings for non-matching groups assert len(result) == 2 diff --git a/tests/helpers/template/extensions/test_string.py b/tests/helpers/template/extensions/test_string.py index 241bf40eef1..88a7ef758d3 100644 --- a/tests/helpers/template/extensions/test_string.py +++ b/tests/helpers/template/extensions/test_string.py @@ -3,7 +3,8 @@ from __future__ import annotations from homeassistant.core import HomeAssistant -from homeassistant.helpers import template + +from tests.helpers.template.helpers import render def test_ordinal(hass: HomeAssistant) -> None: @@ -20,63 +21,48 @@ def test_ordinal(hass: HomeAssistant) -> None: ] for value, expected in tests: - assert ( - template.Template(f"{{{{ {value} | ordinal }}}}", hass).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | ordinal }}}}") == expected def test_slugify(hass: HomeAssistant) -> None: """Test the slugify filter.""" # Test as global function - assert ( - template.Template('{{ slugify("Home Assistant") }}', hass).async_render() - == "home_assistant" - ) + assert render(hass, '{{ slugify("Home Assistant") }}') == "home_assistant" # Test as filter - assert ( - template.Template('{{ "Home Assistant" | slugify }}', hass).async_render() - == "home_assistant" - ) + assert render(hass, '{{ "Home Assistant" | slugify }}') == "home_assistant" # Test with custom separator as global - assert ( - template.Template('{{ slugify("Home Assistant", "-") }}', hass).async_render() - == "home-assistant" - ) + assert render(hass, '{{ slugify("Home Assistant", "-") }}') == "home-assistant" # Test with custom separator as filter - assert ( - template.Template('{{ "Home Assistant" | slugify("-") }}', hass).async_render() - == "home-assistant" - ) + assert render(hass, '{{ "Home Assistant" | slugify("-") }}') == "home-assistant" def test_urlencode(hass: HomeAssistant) -> None: """Test the urlencode method.""" # Test with dictionary - tpl = template.Template( - "{% set dict = {'foo': 'x&y', 'bar': 42} %}{{ dict | urlencode }}", - hass, + + result = render( + hass, "{% set dict = {'foo': 'x&y', 'bar': 42} %}{{ dict | urlencode }}" ) - assert tpl.async_render() == "foo=x%26y&bar=42" + assert result == "foo=x%26y&bar=42" # Test with string - tpl = template.Template( - "{% set string = 'the quick brown fox = true' %}{{ string | urlencode }}", - hass, + + result = render( + hass, "{% set string = 'the quick brown fox = true' %}{{ string | urlencode }}" ) - assert tpl.async_render() == "the%20quick%20brown%20fox%20%3D%20true" + assert result == "the%20quick%20brown%20fox%20%3D%20true" def test_string_functions_with_non_string_input(hass: HomeAssistant) -> None: """Test string functions with non-string input (automatic conversion).""" # Test ordinal with integer - assert template.Template("{{ 42 | ordinal }}", hass).async_render() == "42nd" + assert render(hass, "{{ 42 | ordinal }}") == "42nd" # Test slugify with integer - Note: Jinja2 may return integer for simple cases - result = template.Template("{{ 123 | slugify }}", hass).async_render() + result = render(hass, "{{ 123 | slugify }}") # Accept either string or integer result for simple numeric cases assert result in ["123", 123] @@ -94,10 +80,7 @@ def test_ordinal_edge_cases(hass: HomeAssistant) -> None: ] for value, expected in teens_tests: - assert ( - template.Template(f"{{{{ {value} | ordinal }}}}", hass).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | ordinal }}}}") == expected # Test other numbers ending in 1, 2, 3 other_tests = [ @@ -110,10 +93,7 @@ def test_ordinal_edge_cases(hass: HomeAssistant) -> None: ] for value, expected in other_tests: - assert ( - template.Template(f"{{{{ {value} | ordinal }}}}", hass).async_render() - == expected - ) + assert render(hass, f"{{{{ {value} | ordinal }}}}") == expected def test_slugify_various_separators(hass: HomeAssistant) -> None: @@ -127,38 +107,27 @@ def test_slugify_various_separators(hass: HomeAssistant) -> None: for text, separator, expected in test_cases: # Test as global function - assert ( - template.Template( - f'{{{{ slugify("{text}", "{separator}") }}}}', hass - ).async_render() - == expected - ) + assert render(hass, f'{{{{ slugify("{text}", "{separator}") }}}}') == expected # Test as filter - assert ( - template.Template( - f'{{{{ "{text}" | slugify("{separator}") }}}}', hass - ).async_render() - == expected - ) + assert render(hass, f'{{{{ "{text}" | slugify("{separator}") }}}}') == expected def test_urlencode_various_types(hass: HomeAssistant) -> None: """Test urlencode with various data types.""" # Test with nested dictionary values - tpl = template.Template( - "{% set data = {'key': 'value with spaces', 'num': 123} %}{{ data | urlencode }}", + result = render( hass, + "{% set data = {'key': 'value with spaces', 'num': 123} %}{{ data | urlencode }}", ) - result = tpl.async_render() # URL encoding can have different order, so check both parts are present # Note: urllib.parse.urlencode uses + for spaces in form data assert "key=value+with+spaces" in result assert "num=123" in result # Test with special characters - tpl = template.Template( - "{% set data = {'special': 'a+b=c&d'} %}{{ data | urlencode }}", - hass, + + result = render( + hass, "{% set data = {'special': 'a+b=c&d'} %}{{ data | urlencode }}" ) - assert tpl.async_render() == "special=a%2Bb%3Dc%26d" + assert result == "special=a%2Bb%3Dc%26d" diff --git a/tests/helpers/template/test_init.py b/tests/helpers/template/test_init.py index c4e7c058a83..d7d7b1b5253 100644 --- a/tests/helpers/template/test_init.py +++ b/tests/helpers/template/test_init.py @@ -81,8 +81,7 @@ def _set_up_units(hass: HomeAssistant) -> None: async def test_template_render_missing_hass(hass: HomeAssistant) -> None: """Test template render when hass is not set.""" hass.states.async_set("sensor.test", "23") - template_str = "{{ states('sensor.test') }}" - template_obj = template.Template(template_str, None) + template_obj = template.Template("{{ states('sensor.test') }}", None) template.render_info_cv.set(template.RenderInfo(template_obj)) with pytest.raises(RuntimeError, match="hass not set while rendering"): @@ -96,8 +95,7 @@ async def test_template_render_info_collision(hass: HomeAssistant) -> None: in the wrong thread. """ hass.states.async_set("sensor.test", "23") - template_str = "{{ states('sensor.test') }}" - template_obj = template.Template(template_str, None) + template_obj = template.Template("{{ states('sensor.test') }}", None) template_obj.hass = hass template.render_info_cv.set(template.RenderInfo(template_obj)) @@ -148,29 +146,21 @@ def test_invalid_template(hass: HomeAssistant) -> None: def test_referring_states_by_entity_id(hass: HomeAssistant) -> None: """Test referring states by entity id.""" hass.states.async_set("test.object", "happy") - assert ( - template.Template("{{ states.test.object.state }}", hass).async_render() - == "happy" - ) + assert render(hass, "{{ states.test.object.state }}") == "happy" - assert ( - template.Template('{{ states["test.object"].state }}', hass).async_render() - == "happy" - ) + assert render(hass, '{{ states["test.object"].state }}') == "happy" - assert ( - template.Template('{{ states("test.object") }}', hass).async_render() == "happy" - ) + assert render(hass, '{{ states("test.object") }}') == "happy" def test_invalid_entity_id(hass: HomeAssistant) -> None: """Test referring states by entity id.""" with pytest.raises(TemplateError): - template.Template('{{ states["big.fat..."] }}', hass).async_render() + render(hass, '{{ states["big.fat..."] }}') with pytest.raises(TemplateError): - template.Template('{{ states.test["big.fat..."] }}', hass).async_render() + render(hass, '{{ states.test["big.fat..."] }}') with pytest.raises(TemplateError): - template.Template('{{ states["invalid/domain"] }}', hass).async_render() + render(hass, '{{ states["invalid/domain"] }}') def test_raise_exception_on_error(hass: HomeAssistant) -> None: @@ -246,35 +236,35 @@ async def test_import(hass: HomeAssistant) -> None: assert "test.jinja" in template._get_hass_loader(hass).sources assert "inner/inner_test.jinja" in template._get_hass_loader(hass).sources assert ( - template.Template( - """ - {% import 'test.jinja' as t %} - {{ t.test_macro() }} {{ t.test_variable }} - """, + render( hass, - ).async_render() + """ + {% import 'test.jinja' as t %} + {{ t.test_macro() }} {{ t.test_variable }} + """, + ) == "macro variable" ) assert ( - template.Template( - """ - {% import 'inner/inner_test.jinja' as t %} - {{ t.test_macro() }} {{ t.test_variable }} - """, + render( hass, - ).async_render() + """ + {% import 'inner/inner_test.jinja' as t %} + {{ t.test_macro() }} {{ t.test_variable }} + """, + ) == "inner macro inner variable" ) with pytest.raises(TemplateError): - template.Template( - """ - {% import 'notfound.jinja' as t %} - {{ t.test_macro() }} {{ t.test_variable }} - """, + render( hass, - ).async_render() + """ + {% import 'notfound.jinja' as t %} + {{ t.test_macro() }} {{ t.test_variable }} + """, + ) async def test_import_change(hass: HomeAssistant) -> None: @@ -303,45 +293,30 @@ async def test_import_change(hass: HomeAssistant) -> None: def test_loop_controls(hass: HomeAssistant) -> None: """Test that loop controls are enabled.""" - assert ( - template.Template( - """ - {%- for v in range(10) %} - {%- if v == 1 -%} - {%- continue -%} - {%- elif v == 3 -%} - {%- break -%} - {%- endif -%} - {{ v }} - {%- endfor -%} - """, - hass, - ).async_render() - == "02" - ) + tpl = """ + {%- for v in range(10) %} + {%- if v == 1 -%} + {%- continue -%} + {%- elif v == 3 -%} + {%- break -%} + {%- endif -%} + {{ v }} + {%- endfor -%} + """ + assert render(hass, tpl) == "02" def test_float_function(hass: HomeAssistant) -> None: """Test float function.""" hass.states.async_set("sensor.temperature", "12") - assert ( - template.Template( - "{{ float(states.sensor.temperature.state) }}", hass - ).async_render() - == 12.0 - ) + assert render(hass, "{{ float(states.sensor.temperature.state) }}") == 12.0 - assert ( - template.Template( - "{{ float(states.sensor.temperature.state) > 11 }}", hass - ).async_render() - is True - ) + assert render(hass, "{{ float(states.sensor.temperature.state) > 11 }}") is True # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ float('forgiving') }}", hass).async_render() + render(hass, "{{ float('forgiving') }}") # Test handling of default return value assert render(hass, "{{ float('bad', 1) }}") == 1 @@ -451,31 +426,18 @@ def test_bool_filter(hass: HomeAssistant) -> None: ) def test_isnumber(hass: HomeAssistant, value, expected) -> None: """Test is_number.""" - assert ( - template.Template("{{ is_number(value) }}", hass).async_render({"value": value}) - == expected - ) - assert ( - template.Template("{{ value | is_number }}", hass).async_render( - {"value": value} - ) - == expected - ) - assert ( - template.Template("{{ value is is_number }}", hass).async_render( - {"value": value} - ) - == expected - ) + assert render(hass, "{{ is_number(value) }}", {"value": value}) == expected + assert render(hass, "{{ value | is_number }}", {"value": value}) == expected + assert render(hass, "{{ value is is_number }}", {"value": value}) == expected def test_converting_datetime_to_iterable(hass: HomeAssistant) -> None: """Test converting a datetime to an iterable raises an error.""" dt_ = datetime(2020, 1, 1, 0, 0, 0) with pytest.raises(TemplateError): - template.Template("{{ tuple(value) }}", hass).async_render({"value": dt_}) + render(hass, "{{ tuple(value) }}", {"value": dt_}) with pytest.raises(TemplateError): - template.Template("{{ set(value) }}", hass).async_render({"value": dt_}) + render(hass, "{{ set(value) }}", {"value": dt_}) @pytest.mark.parametrize( @@ -494,51 +456,31 @@ def test_converting_datetime_to_iterable(hass: HomeAssistant) -> None: ) def test_is_datetime(hass: HomeAssistant, value, expected) -> None: """Test is datetime.""" - assert ( - template.Template("{{ value is datetime }}", hass).async_render( - {"value": value} - ) - == expected - ) + assert render(hass, "{{ value is datetime }}", {"value": value}) == expected def test_rounding_value(hass: HomeAssistant) -> None: """Test rounding value.""" hass.states.async_set("sensor.temperature", 12.78) - assert ( - template.Template( - "{{ states.sensor.temperature.state | round(1) }}", hass - ).async_render() - == 12.8 - ) + assert render(hass, "{{ states.sensor.temperature.state | round(1) }}") == 12.8 assert ( - template.Template( - "{{ states.sensor.temperature.state | multiply(10) | round }}", hass - ).async_render() + render(hass, "{{ states.sensor.temperature.state | multiply(10) | round }}") == 128 ) assert ( - template.Template( - '{{ states.sensor.temperature.state | round(1, "floor") }}', hass - ).async_render() + render(hass, '{{ states.sensor.temperature.state | round(1, "floor") }}') == 12.7 ) assert ( - template.Template( - '{{ states.sensor.temperature.state | round(1, "ceil") }}', hass - ).async_render() - == 12.8 + render(hass, '{{ states.sensor.temperature.state | round(1, "ceil") }}') == 12.8 ) assert ( - template.Template( - '{{ states.sensor.temperature.state | round(1, "half") }}', hass - ).async_render() - == 13.0 + render(hass, '{{ states.sensor.temperature.state | round(1, "half") }}') == 13.0 ) @@ -546,10 +488,10 @@ def test_rounding_value_on_error(hass: HomeAssistant) -> None: """Test rounding value handling of error.""" # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ None | round }}", hass).async_render() + render(hass, "{{ None | round }}") with pytest.raises(TemplateError): - template.Template('{{ "no_number" | round }}', hass).async_render() + render(hass, '{{ "no_number" | round }}') # Test handling of default return value assert render(hass, "{{ 'no_number' | round(default=1) }}") == 1 @@ -560,16 +502,11 @@ def test_multiply(hass: HomeAssistant) -> None: tests = {10: 100} for inp, out in tests.items(): - assert ( - template.Template( - f"{{{{ {inp} | multiply(10) | round }}}}", hass - ).async_render() - == out - ) + assert render(hass, f"{{{{ {inp} | multiply(10) | round }}}}") == out # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ abcd | multiply(10) }}", hass).async_render() + render(hass, "{{ abcd | multiply(10) }}") # Test handling of default return value assert render(hass, "{{ 'no_number' | multiply(10, 1) }}") == 1 @@ -581,14 +518,11 @@ def test_add(hass: HomeAssistant) -> None: tests = {10: 42} for inp, out in tests.items(): - assert ( - template.Template(f"{{{{ {inp} | add(32) | round }}}}", hass).async_render() - == out - ) + assert render(hass, f"{{{{ {inp} | add(32) | round }}}}") == out # Test handling of invalid input with pytest.raises(TemplateError): - template.Template("{{ abcd | add(10) }}", hass).async_render() + render(hass, "{{ abcd | add(10) }}") # Test handling of default return value assert render(hass, "{{ 'no_number' | add(10, 1) }}") == 1 @@ -597,101 +531,71 @@ def test_add(hass: HomeAssistant) -> None: def test_apply(hass: HomeAssistant) -> None: """Test apply.""" - assert template.Template( - """ - {%- macro add_foo(arg) -%} - {{arg}}foo - {%- endmacro -%} - {{ ["a", "b", "c"] | map('apply', add_foo) | list }} - """, - hass, - ).async_render() == ["afoo", "bfoo", "cfoo"] + tpl = """ + {%- macro add_foo(arg) -%} + {{arg}}foo + {%- endmacro -%} + {{ ["a", "b", "c"] | map('apply', add_foo) | list }} + """ + assert render(hass, tpl) == ["afoo", "bfoo", "cfoo"] - assert template.Template( - """ - {{ ['1', '2', '3', '4', '5'] | map('apply', int) | list }} - """, - hass, - ).async_render() == [1, 2, 3, 4, 5] + assert render( + hass, "{{ ['1', '2', '3', '4', '5'] | map('apply', int) | list }}" + ) == [1, 2, 3, 4, 5] def test_apply_macro_with_arguments(hass: HomeAssistant) -> None: """Test apply macro with positional, named, and mixed arguments.""" # Test macro with positional arguments - assert ( - template.Template( - """ - {%- macro add_numbers(a, b, c) -%} - {{ a + b + c }} - {%- endmacro -%} - {{ apply(5, add_numbers, 10, 15) }} - """, - hass, - ).async_render() - == 30 - ) + tpl = """ + {%- macro add_numbers(a, b, c) -%} + {{ a + b + c }} + {%- endmacro -%} + {{ apply(5, add_numbers, 10, 15) }} + """ + assert render(hass, tpl) == 30 # Test macro with named arguments - assert ( - template.Template( - """ - {%- macro greet(name, greeting="Hello") -%} - {{ greeting }}, {{ name }}! - {%- endmacro -%} - {{ apply("World", greet, greeting="Hi") }} - """, - hass, - ).async_render() - == "Hi, World!" - ) + tpl = """ + {%- macro greet(name, greeting="Hello") -%} + {{ greeting }}, {{ name }}! + {%- endmacro -%} + {{ apply("World", greet, greeting="Hi") }} + """ + assert render(hass, tpl) == "Hi, World!" # Test macro with mixed arguments - assert ( - template.Template( - """ - {%- macro format_message(prefix, name, suffix="!") -%} - {{ prefix }} {{ name }}{{ suffix }} - {%- endmacro -%} - {{ apply("Welcome", format_message, "John", suffix="...") }} - """, - hass, - ).async_render() - == "Welcome John..." - ) + tpl = """ + {%- macro format_message(prefix, name, suffix="!") -%} + {{ prefix }} {{ name }}{{ suffix }} + {%- endmacro -%} + {{ apply("Welcome", format_message, "John", suffix="...") }} + """ + assert render(hass, tpl) == "Welcome John..." def test_as_function(hass: HomeAssistant) -> None: """Test as_function.""" - assert ( - template.Template( - """ - {%- macro macro_double(num, returns) -%} - {%- do returns(num * 2) -%} - {%- endmacro -%} - {%- set double = macro_double | as_function -%} - {{ double(5) }} - """, - hass, - ).async_render() - == 10 - ) + tpl = """ + {%- macro macro_double(num, returns) -%} + {%- do returns(num * 2) -%} + {%- endmacro -%} + {%- set double = macro_double | as_function -%} + {{ double(5) }} + """ + assert render(hass, tpl) == 10 def test_as_function_no_arguments(hass: HomeAssistant) -> None: """Test as_function with no arguments.""" - assert ( - template.Template( - """ - {%- macro macro_get_hello(returns) -%} - {%- do returns("Hello") -%} - {%- endmacro -%} - {%- set get_hello = macro_get_hello | as_function -%} - {{ get_hello() }} - """, - hass, - ).async_render() - == "Hello" - ) + tpl = """ + {%- macro macro_get_hello(returns) -%} + {%- do returns("Hello") -%} + {%- endmacro -%} + {%- set get_hello = macro_get_hello | as_function -%} + {{ get_hello() }} + """ + assert render(hass, tpl) == "Hello" def test_strptime(hass: HomeAssistant) -> None: @@ -711,7 +615,7 @@ def test_strptime(hass: HomeAssistant) -> None: temp = f"{{{{ strptime('{inp}', '{fmt}') }}}}" - assert template.Template(temp, hass).async_render() == expected + assert render(hass, temp) == expected # Test handling of invalid input invalid_tests = [ @@ -723,7 +627,7 @@ def test_strptime(hass: HomeAssistant) -> None: temp = f"{{{{ strptime('{inp}', '{fmt}') }}}}" with pytest.raises(TemplateError): - template.Template(temp, hass).async_render() + render(hass, temp) # Test handling of default return value assert render(hass, "{{ strptime('invalid', '%Y', 1) }}") == 1 @@ -749,7 +653,7 @@ async def test_timestamp_custom(hass: HomeAssistant) -> None: else: fil = "timestamp_custom" - assert template.Template(f"{{{{ {inp} | {fil} }}}}", hass).async_render() == out + assert render(hass, f"{{{{ {inp} | {fil} }}}}") == out # Test handling of invalid input invalid_tests = [ @@ -765,7 +669,7 @@ async def test_timestamp_custom(hass: HomeAssistant) -> None: fil = "timestamp_custom" with pytest.raises(TemplateError): - template.Template(f"{{{{ {inp} | {fil} }}}}", hass).async_render() + render(hass, f"{{{{ {inp} | {fil} }}}}") # Test handling of default return value assert render(hass, "{{ None | timestamp_custom('invalid', True, 1) }}") == 1 @@ -780,10 +684,7 @@ async def test_timestamp_local(hass: HomeAssistant) -> None: ] for inp, out in tests: - assert ( - template.Template(f"{{{{ {inp} | timestamp_local }}}}", hass).async_render() - == out - ) + assert render(hass, f"{{{{ {inp} | timestamp_local }}}}") == out # Test handling of invalid input invalid_tests = [ @@ -792,7 +693,7 @@ async def test_timestamp_local(hass: HomeAssistant) -> None: for inp in invalid_tests: with pytest.raises(TemplateError): - template.Template(f"{{{{ {inp} | timestamp_local }}}}", hass).async_render() + render(hass, f"{{{{ {inp} | timestamp_local }}}}") # Test handling of default return value assert render(hass, "{{ None | timestamp_local(1) }}") == 1 @@ -815,14 +716,8 @@ def test_as_datetime(hass: HomeAssistant, input) -> None: expected = dt_util.parse_datetime(input) if expected is not None: expected = str(expected) - assert ( - template.Template(f"{{{{ as_datetime('{input}') }}}}", hass).async_render() - == expected - ) - assert ( - template.Template(f"{{{{ '{input}' | as_datetime }}}}", hass).async_render() - == expected - ) + assert render(hass, f"{{{{ as_datetime('{input}') }}}}") == expected + assert render(hass, f"{{{{ '{input}' | as_datetime }}}}") == expected @pytest.mark.parametrize( @@ -839,22 +734,10 @@ def test_as_datetime_from_timestamp( output: str, ) -> None: """Test converting a UNIX timestamp to a date object.""" - assert ( - template.Template(f"{{{{ as_datetime({input}) }}}}", hass).async_render() - == output - ) - assert ( - template.Template(f"{{{{ {input} | as_datetime }}}}", hass).async_render() - == output - ) - assert ( - template.Template(f"{{{{ as_datetime('{input}') }}}}", hass).async_render() - == output - ) - assert ( - template.Template(f"{{{{ '{input}' | as_datetime }}}}", hass).async_render() - == output - ) + assert render(hass, f"{{{{ as_datetime({input}) }}}}") == output + assert render(hass, f"{{{{ {input} | as_datetime }}}}") == output + assert render(hass, f"{{{{ as_datetime('{input}') }}}}") == output + assert render(hass, f"{{{{ '{input}' | as_datetime }}}}") == output @pytest.mark.parametrize( @@ -875,15 +758,9 @@ def test_as_datetime_from_datetime( ) -> None: """Test using datetime.datetime or datetime.date objects as input.""" - assert ( - template.Template(f"{input}{{{{ dt | as_datetime }}}}", hass).async_render() - == output - ) + assert render(hass, f"{input}{{{{ dt | as_datetime }}}}") == output - assert ( - template.Template(f"{input}{{{{ as_datetime(dt) }}}}", hass).async_render() - == output - ) + assert render(hass, f"{input}{{{{ as_datetime(dt) }}}}") == output @pytest.mark.parametrize( @@ -900,18 +777,8 @@ def test_as_datetime_default( ) -> None: """Test invalid input and return default value.""" - assert ( - template.Template( - f"{{{{ as_datetime({input}, default={default}) }}}}", hass - ).async_render() - == output - ) - assert ( - template.Template( - f"{{{{ {input} | as_datetime({default}) }}}}", hass - ).async_render() - == output - ) + assert render(hass, f"{{{{ as_datetime({input}, default={default}) }}}}") == output + assert render(hass, f"{{{{ {input} | as_datetime({default}) }}}}") == output def test_as_local(hass: HomeAssistant) -> None: @@ -919,12 +786,12 @@ def test_as_local(hass: HomeAssistant) -> None: hass.states.async_set("test.object", "available") last_updated = hass.states.get("test.object").last_updated - assert template.Template( - "{{ as_local(states.test.object.last_updated) }}", hass - ).async_render() == str(dt_util.as_local(last_updated)) - assert template.Template( - "{{ states.test.object.last_updated | as_local }}", hass - ).async_render() == str(dt_util.as_local(last_updated)) + assert render(hass, "{{ as_local(states.test.object.last_updated) }}") == str( + dt_util.as_local(last_updated) + ) + assert render(hass, "{{ states.test.object.last_updated | as_local }}") == str( + dt_util.as_local(last_updated) + ) def test_to_json(hass: HomeAssistant) -> None: @@ -933,27 +800,27 @@ def test_to_json(hass: HomeAssistant) -> None: # Note that we're not testing the actual json.loads and json.dumps methods, # only the filters, so we don't need to be exhaustive with our sample JSON. expected_result = {"Foo": "Bar"} - actual_result = template.Template( - "{{ {'Foo': 'Bar'} | to_json }}", hass - ).async_render() + actual_result = render(hass, "{{ {'Foo': 'Bar'} | to_json }}") assert actual_result == expected_result expected_result = orjson.dumps({"Foo": "Bar"}, option=orjson.OPT_INDENT_2).decode() - actual_result = template.Template( - "{{ {'Foo': 'Bar'} | to_json(pretty_print=True) }}", hass - ).async_render(parse_result=False) + actual_result = render( + hass, "{{ {'Foo': 'Bar'} | to_json(pretty_print=True) }}", parse_result=False + ) assert actual_result == expected_result expected_result = orjson.dumps( {"Z": 26, "A": 1, "M": 13}, option=orjson.OPT_SORT_KEYS ).decode() - actual_result = template.Template( - "{{ {'Z': 26, 'A': 1, 'M': 13} | to_json(sort_keys=True) }}", hass - ).async_render(parse_result=False) + actual_result = render( + hass, + "{{ {'Z': 26, 'A': 1, 'M': 13} | to_json(sort_keys=True) }}", + parse_result=False, + ) assert actual_result == expected_result with pytest.raises(TemplateError): - template.Template("{{ {'Foo': now()} | to_json }}", hass).async_render() + render(hass, "{{ {'Foo': now()} | to_json }}") # Test special case where substring class cannot be rendered # See: https://github.com/ijl/orjson/issues/445 @@ -966,9 +833,12 @@ def test_to_json(hass: HomeAssistant) -> None: MyStr("mykey1"): 11.0, MyStr("mykey3"): ["opt3b", "opt3a"], } - actual_result = template.Template( - "{{ test_dict | to_json(sort_keys=True) }}", hass - ).async_render(parse_result=False, variables={"test_dict": test_dict}) + actual_result = render( + hass, + "{{ test_dict | to_json(sort_keys=True) }}", + {"test_dict": test_dict}, + parse_result=False, + ) assert actual_result == expected_result @@ -977,26 +847,25 @@ def test_to_json_ensure_ascii(hass: HomeAssistant) -> None: # Note that we're not testing the actual json.loads and json.dumps methods, # only the filters, so we don't need to be exhaustive with our sample JSON. - actual_value_ascii = template.Template( - "{{ 'Bar ҝ éèà' | to_json(ensure_ascii=True) }}", hass - ).async_render() + actual_value_ascii = render(hass, "{{ 'Bar ҝ éèà' | to_json(ensure_ascii=True) }}") assert actual_value_ascii == '"Bar \\u049d \\u00e9\\u00e8\\u00e0"' - actual_value = template.Template( - "{{ 'Bar ҝ éèà' | to_json(ensure_ascii=False) }}", hass - ).async_render() + actual_value = render(hass, "{{ 'Bar ҝ éèà' | to_json(ensure_ascii=False) }}") assert actual_value == '"Bar ҝ éèà"' expected_result = json.dumps({"Foo": "Bar"}, indent=2) - actual_result = template.Template( - "{{ {'Foo': 'Bar'} | to_json(pretty_print=True, ensure_ascii=True) }}", hass - ).async_render(parse_result=False) + actual_result = render( + hass, + "{{ {'Foo': 'Bar'} | to_json(pretty_print=True, ensure_ascii=True) }}", + parse_result=False, + ) assert actual_result == expected_result expected_result = json.dumps({"Z": 26, "A": 1, "M": 13}, sort_keys=True) - actual_result = template.Template( - "{{ {'Z': 26, 'A': 1, 'M': 13} | to_json(sort_keys=True, ensure_ascii=True) }}", + actual_result = render( hass, - ).async_render(parse_result=False) + "{{ {'Z': 26, 'A': 1, 'M': 13} | to_json(sort_keys=True, ensure_ascii=True) }}", + parse_result=False, + ) assert actual_result == expected_result @@ -1006,32 +875,25 @@ def test_from_json(hass: HomeAssistant) -> None: # Note that we're not testing the actual json.loads and json.dumps methods, # only the filters, so we don't need to be exhaustive with our sample JSON. expected_result = "Bar" - actual_result = template.Template( - '{{ (\'{"Foo": "Bar"}\' | from_json).Foo }}', hass - ).async_render() + actual_result = render(hass, '{{ (\'{"Foo": "Bar"}\' | from_json).Foo }}') assert actual_result == expected_result info = render_to_info(hass, "{{ 'garbage string' | from_json }}") with pytest.raises(TemplateError, match="no default was specified"): info.result() - actual_result = template.Template( - "{{ 'garbage string' | from_json('Bar') }}", hass - ).async_render() + actual_result = render(hass, "{{ 'garbage string' | from_json('Bar') }}") assert actual_result == expected_result def test_ord(hass: HomeAssistant) -> None: """Test the ord filter.""" - assert template.Template('{{ "d" | ord }}', hass).async_render() == 100 + assert render(hass, '{{ "d" | ord }}') == 100 def test_from_hex(hass: HomeAssistant) -> None: """Test the fromhex filter.""" - assert ( - template.Template("{{ '0F010003' | from_hex }}", hass).async_render() - == b"\x0f\x01\x00\x03" - ) + assert render(hass, "{{ '0F010003' | from_hex }}") == b"\x0f\x01\x00\x03" def test_timestamp_utc(hass: HomeAssistant) -> None: @@ -1043,10 +905,7 @@ def test_timestamp_utc(hass: HomeAssistant) -> None: ] for inp, out in tests: - assert ( - template.Template(f"{{{{ {inp} | timestamp_utc }}}}", hass).async_render() - == out - ) + assert render(hass, f"{{{{ {inp} | timestamp_utc }}}}") == out # Test handling of invalid input invalid_tests = [ @@ -1055,7 +914,7 @@ def test_timestamp_utc(hass: HomeAssistant) -> None: for inp in invalid_tests: with pytest.raises(TemplateError): - template.Template(f"{{{{ {inp} | timestamp_utc }}}}", hass).async_render() + render(hass, f"{{{{ {inp} | timestamp_utc }}}}") # Test handling of default return value assert render(hass, "{{ None | timestamp_utc(1) }}") == 1 @@ -1065,17 +924,17 @@ def test_timestamp_utc(hass: HomeAssistant) -> None: def test_as_timestamp(hass: HomeAssistant) -> None: """Test the as_timestamp function.""" with pytest.raises(TemplateError): - template.Template('{{ as_timestamp("invalid") }}', hass).async_render() + render(hass, '{{ as_timestamp("invalid") }}') hass.states.async_set("test.object", None) with pytest.raises(TemplateError): - template.Template("{{ as_timestamp(states.test.object) }}", hass).async_render() + render(hass, "{{ as_timestamp(states.test.object) }}") tpl = ( '{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", ' '"%Y-%m-%dT%H:%M:%S%z")) }}' ) - assert template.Template(tpl, hass).async_render() == 1706951424.0 + assert render(hass, tpl) == 1706951424.0 # Test handling of default return value assert render(hass, "{{ 'invalid' | as_timestamp(1) }}") == 1 @@ -1096,12 +955,12 @@ def test_random_every_time(test_choice, hass: HomeAssistant) -> None: def test_passing_vars_as_keywords(hass: HomeAssistant) -> None: """Test passing variables as keywords.""" - assert template.Template("{{ hello }}", hass).async_render(hello=127) == 127 + assert render(hass, "{{ hello }}", hello=127) == 127 def test_passing_vars_as_vars(hass: HomeAssistant) -> None: """Test passing variables as variables.""" - assert template.Template("{{ hello }}", hass).async_render({"hello": 127}) == 127 + assert render(hass, "{{ hello }}", {"hello": 127}) == 127 def test_passing_vars_as_list(hass: HomeAssistant) -> None: @@ -1113,29 +972,20 @@ def test_passing_vars_as_list(hass: HomeAssistant) -> None: def test_passing_vars_as_list_element(hass: HomeAssistant) -> None: """Test passing variables as list.""" - assert ( - template.render_complex( - template.Template("{{ hello[1] }}", hass), {"hello": ["foo", "bar"]} - ) - == "bar" - ) + tpl = template.Template("{{ hello[1] }}", hass) + assert template.render_complex(tpl, {"hello": ["foo", "bar"]}) == "bar" def test_passing_vars_as_dict_element(hass: HomeAssistant) -> None: """Test passing variables as list.""" - assert ( - template.render_complex( - template.Template("{{ hello.foo }}", hass), {"hello": {"foo": "bar"}} - ) - == "bar" - ) + tpl = template.Template("{{ hello.foo }}", hass) + assert template.render_complex(tpl, {"hello": {"foo": "bar"}}) == "bar" def test_passing_vars_as_dict(hass: HomeAssistant) -> None: """Test passing variables as list.""" - assert template.render_complex( - template.Template("{{ hello }}", hass), {"hello": {"foo": "bar"}} - ) == {"foo": "bar"} + tpl = template.Template("{{ hello }}", hass) + assert template.render_complex(tpl, {"hello": {"foo": "bar"}}) == {"foo": "bar"} def test_render_with_possible_json_value_with_valid_json(hass: HomeAssistant) -> None: @@ -1194,9 +1044,7 @@ def test_render_with_possible_json_value_undefined_json_error_value( def test_render_with_possible_json_value_non_string_value(hass: HomeAssistant) -> None: """Render with possible JSON value with non-string value.""" tpl = template.Template( - """ -{{ strptime(value~'+0000', '%Y-%m-%d %H:%M:%S%z') }} - """, + """{{ strptime(value~'+0000', '%Y-%m-%d %H:%M:%S%z') }}""", hass, ) value = datetime(2019, 1, 18, 12, 13, 14) @@ -1227,10 +1075,11 @@ def test_render_with_possible_json_value_and_dont_parse_result( def test_if_state_exists(hass: HomeAssistant) -> None: """Test if state exists works.""" hass.states.async_set("test.object", "available") - tpl = template.Template( - "{% if states.test.object %}exists{% else %}not exists{% endif %}", hass + + result = render( + hass, "{% if states.test.object %}exists{% else %}not exists{% endif %}" ) - assert tpl.async_render() == "exists" + assert result == "exists" def test_is_hidden_entity( @@ -1242,116 +1091,80 @@ def test_is_hidden_entity( "sensor", "mock", "hidden", hidden_by=er.RegistryEntryHider.USER ) visible_entity = entity_registry.async_get_or_create("sensor", "mock", "visible") - assert template.Template( - f"{{{{ is_hidden_entity('{hidden_entity.entity_id}') }}}}", - hass, - ).async_render() + assert render(hass, f"{{{{ is_hidden_entity('{hidden_entity.entity_id}') }}}}") - assert not template.Template( - f"{{{{ is_hidden_entity('{visible_entity.entity_id}') }}}}", - hass, - ).async_render() + assert not render(hass, f"{{{{ is_hidden_entity('{visible_entity.entity_id}') }}}}") - assert not template.Template( + assert not render( + hass, f"{{{{ ['{visible_entity.entity_id}'] | select('is_hidden_entity') | first }}}}", - hass, - ).async_render() + ) def test_is_state(hass: HomeAssistant) -> None: """Test is_state method.""" hass.states.async_set("test.object", "available") - tpl = template.Template( - """ -{% if is_state("test.object", "available") %}yes{% else %}no{% endif %} - """, - hass, - ) - assert tpl.async_render() == "yes" - tpl = template.Template( - """ -{{ is_state("test.noobject", "available") }} - """, - hass, + result = render( + hass, '{% if is_state("test.object", "available") %}yes{% else %}no{% endif %}' ) - assert tpl.async_render() is False + assert result == "yes" - tpl = template.Template( - """ -{% if "test.object" is is_state("available") %}yes{% else %}no{% endif %} - """, - hass, - ) - assert tpl.async_render() == "yes" + result = render(hass, """{{ is_state("test.noobject", "available") }}""") + assert result is False - tpl = template.Template( - """ -{{ ['test.object'] | select("is_state", "available") | first | default }} - """, + result = render( hass, + '{% if "test.object" is is_state("available") %}yes{% else %}no{% endif %}', ) - assert tpl.async_render() == "test.object" + assert result == "yes" - tpl = template.Template( - """ -{{ is_state("test.object", ["on", "off", "available"]) }} - """, + result = render( hass, + """{{ ['test.object'] | select("is_state", "available") | first | default }}""", ) - assert tpl.async_render() is True + assert result == "test.object" + + result = render(hass, '{{ is_state("test.object", ["on", "off", "available"]) }}') + assert result is True def test_is_state_attr(hass: HomeAssistant) -> None: """Test is_state_attr method.""" hass.states.async_set("test.object", "available", {"mode": "on", "exists": None}) - tpl = template.Template( - """ -{% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %} - """, - hass, - ) - assert tpl.async_render() == "yes" - tpl = template.Template( - """ -{{ is_state_attr("test.noobject", "mode", "on") }} - """, + result = render( hass, + """{% if is_state_attr("test.object", "mode", "on") %}yes{% else %}no{% endif %}""", ) - assert tpl.async_render() is False + assert result == "yes" - tpl = template.Template( - """ -{% if "test.object" is is_state_attr("mode", "on") %}yes{% else %}no{% endif %} - """, - hass, - ) - assert tpl.async_render() == "yes" + result = render(hass, """{{ is_state_attr("test.noobject", "mode", "on") }}""") + assert result is False - tpl = template.Template( - """ -{{ ['test.object'] | select("is_state_attr", "mode", "on") | first | default }} - """, + result = render( hass, + """{% if "test.object" is is_state_attr("mode", "on") %}yes{% else %}no{% endif %}""", ) - assert tpl.async_render() == "test.object" + assert result == "yes" - tpl = template.Template( - """ -{% if is_state_attr("test.object", "exists", None) %}yes{% else %}no{% endif %} - """, + result = render( hass, + """{{ ['test.object'] | select("is_state_attr", "mode", "on") | first | default }}""", ) - assert tpl.async_render() == "yes" + assert result == "test.object" - tpl = template.Template( - """ -{% if is_state_attr("test.object", "noexist", None) %}yes{% else %}no{% endif %} - """, + result = render( hass, + """{% if is_state_attr("test.object", "exists", None) %}yes{% else %}no{% endif %}""", ) - assert tpl.async_render() == "no" + assert result == "yes" + + result = render( + hass, + """{% if is_state_attr("test.object", "noexist", None) %}yes{% else %}no{% endif %}""", + ) + assert result == "no" def test_state_attr(hass: HomeAssistant) -> None: @@ -1359,63 +1172,47 @@ def test_state_attr(hass: HomeAssistant) -> None: hass.states.async_set( "test.object", "available", {"effect": "action", "mode": "on"} ) - tpl = template.Template( - """ -{% if state_attr("test.object", "mode") == "on" %}yes{% else %}no{% endif %} - """, - hass, - ) - assert tpl.async_render() == "yes" - tpl = template.Template( - """ -{{ state_attr("test.noobject", "mode") == None }} - """, + result = render( hass, + """{% if state_attr("test.object", "mode") == "on" %}yes{% else %}no{% endif %}""", ) - assert tpl.async_render() is True + assert result == "yes" - tpl = template.Template( - """ -{% if "test.object" | state_attr("mode") == "on" %}yes{% else %}no{% endif %} - """, - hass, - ) - assert tpl.async_render() == "yes" + result = render(hass, """{{ state_attr("test.noobject", "mode") == None }}""") + assert result is True - tpl = template.Template( - """ -{{ ['test.object'] | map("state_attr", "effect") | first | default }} - """, + result = render( hass, + """{% if "test.object" | state_attr("mode") == "on" %}yes{% else %}no{% endif %}""", ) - assert tpl.async_render() == "action" + assert result == "yes" + + result = render( + hass, + """{{ ['test.object'] | map("state_attr", "effect") | first | default }}""", + ) + assert result == "action" def test_states_function(hass: HomeAssistant) -> None: """Test using states as a function.""" hass.states.async_set("test.object", "available") - tpl = template.Template('{{ states("test.object") }}', hass) - assert tpl.async_render() == "available" - tpl2 = template.Template('{{ states("test.object2") }}', hass) - assert tpl2.async_render() == "unknown" + result = render(hass, '{{ states("test.object") }}') + assert result == "available" - tpl = template.Template( - """ -{% if "test.object" | states == "available" %}yes{% else %}no{% endif %} - """, + result = render(hass, '{{ states("test.object2") }}') + assert result == "unknown" + + result = render( hass, + """{% if "test.object" | states == "available" %}yes{% else %}no{% endif %}""", ) - assert tpl.async_render() == "yes" + assert result == "yes" - tpl = template.Template( - """ -{{ ['test.object'] | map("states") | first | default }} - """, - hass, - ) - assert tpl.async_render() == "available" + result = render(hass, """{{ ['test.object'] | map("states") | first | default }}""") + assert result == "available" async def test_state_translated( @@ -1470,36 +1267,30 @@ async def test_state_translated( ) hass.states.async_set("light.hue_5678", "on", attributes={}) - tpl = template.Template( - '{{ state_translated("switch.without_translations") }}', hass - ) - assert tpl.async_render() == "on" + result = render(hass, '{{ state_translated("switch.without_translations") }}') + assert result == "on" - tp2 = template.Template( - '{{ state_translated("binary_sensor.without_device_class") }}', hass + result = render( + hass, '{{ state_translated("binary_sensor.without_device_class") }}' ) - assert tp2.async_render() == "On" + assert result == "On" - tpl3 = template.Template( - '{{ state_translated("binary_sensor.with_device_class") }}', hass - ) - assert tpl3.async_render() == "Detected" + result = render(hass, '{{ state_translated("binary_sensor.with_device_class") }}') + assert result == "Detected" - tpl4 = template.Template( - '{{ state_translated("binary_sensor.with_unknown_device_class") }}', hass + result = render( + hass, '{{ state_translated("binary_sensor.with_unknown_device_class") }}' ) - assert tpl4.async_render() == "On" + assert result == "On" with pytest.raises(TemplateError): - template.Template( - '{{ state_translated("contextfunction") }}', hass - ).async_render() + render(hass, '{{ state_translated("contextfunction") }}') - tpl6 = template.Template('{{ state_translated("switch.invalid") }}', hass) - assert tpl6.async_render() == "unknown" + result = render(hass, '{{ state_translated("switch.invalid") }}') + assert result == "unknown" with pytest.raises(TemplateError): - template.Template('{{ state_translated("-invalid") }}', hass).async_render() + render(hass, '{{ state_translated("-invalid") }}') def mock_get_cached_translations( _hass: HomeAssistant, @@ -1517,14 +1308,14 @@ async def test_state_translated( "homeassistant.helpers.translation.async_get_cached_translations", side_effect=mock_get_cached_translations, ): - tpl8 = template.Template('{{ state_translated("light.hue_5678") }}', hass) - assert tpl8.async_render() == "state_is_on" + result = render(hass, '{{ state_translated("light.hue_5678") }}') + assert result == "state_is_on" - tpl11 = template.Template('{{ state_translated("domain.is_unavailable") }}', hass) - assert tpl11.async_render() == "unavailable" + result = render(hass, '{{ state_translated("domain.is_unavailable") }}') + assert result == "unavailable" - tpl12 = template.Template('{{ state_translated("domain.is_unknown") }}', hass) - assert tpl12.async_render() == "unknown" + result = render(hass, '{{ state_translated("domain.is_unknown") }}') + assert result == "unknown" def test_has_value(hass: HomeAssistant) -> None: @@ -1532,37 +1323,19 @@ def test_has_value(hass: HomeAssistant) -> None: hass.states.async_set("test.value1", 1) hass.states.async_set("test.unavailable", STATE_UNAVAILABLE) - tpl = template.Template( - """ -{{ has_value("test.value1") }} - """, - hass, - ) - assert tpl.async_render() is True + result = render(hass, """{{ has_value("test.value1") }}""") + assert result is True - tpl = template.Template( - """ -{{ has_value("test.unavailable") }} - """, - hass, - ) - assert tpl.async_render() is False + result = render(hass, """{{ has_value("test.unavailable") }}""") + assert result is False - tpl = template.Template( - """ -{{ has_value("test.unknown") }} - """, - hass, - ) - assert tpl.async_render() is False + result = render(hass, """{{ has_value("test.unknown") }}""") + assert result is False - tpl = template.Template( - """ -{% if "test.value1" is has_value %}yes{% else %}no{% endif %} - """, - hass, + result = render( + hass, """{% if "test.value1" is has_value %}yes{% else %}no{% endif %}""" ) - assert tpl.async_render() == "yes" + assert result == "yes" @patch( @@ -1573,7 +1346,7 @@ def test_now(mock_is_safe, hass: HomeAssistant) -> None: """Test now method.""" now = dt_util.now() with freeze_time(now): - info = template.Template("{{ now().isoformat() }}", hass).async_render_to_info() + info = render_to_info(hass, "{{ now().isoformat() }}") assert now.isoformat() == info.result() assert info.has_time is True @@ -1587,9 +1360,7 @@ def test_utcnow(mock_is_safe, hass: HomeAssistant) -> None: """Test now method.""" utcnow = dt_util.utcnow() with freeze_time(utcnow): - info = template.Template( - "{{ utcnow().isoformat() }}", hass - ).async_render_to_info() + info = render_to_info(hass, "{{ utcnow().isoformat() }}") assert utcnow.isoformat() == info.result() assert info.has_time is True @@ -1627,36 +1398,22 @@ async def test_today_at( await hass.config.async_set_time_zone(timezone_str) - result = template.Template( - "{{ today_at('10:00').isoformat() }}", - hass, - ).async_render() + result = render(hass, "{{ today_at('10:00').isoformat() }}") assert result == expected - result = template.Template( - "{{ today_at('10:00:00').isoformat() }}", - hass, - ).async_render() + result = render(hass, "{{ today_at('10:00:00').isoformat() }}") assert result == expected - result = template.Template( - "{{ ('10:00:00' | today_at).isoformat() }}", - hass, - ).async_render() + result = render(hass, "{{ ('10:00:00' | today_at).isoformat() }}") assert result == expected - result = template.Template( - "{{ today_at().isoformat() }}", - hass, - ).async_render() + result = render(hass, "{{ today_at().isoformat() }}") assert result == expected_midnight with pytest.raises(TemplateError): - template.Template("{{ today_at('bad') }}", hass).async_render() + render(hass, "{{ today_at('bad') }}") - info = template.Template( - "{{ today_at('10:00').isoformat() }}", hass - ).async_render_to_info() + info = render_to_info(hass, "{{ today_at('10:00').isoformat() }}") assert info.has_time is True freezer.stop() @@ -1674,12 +1431,10 @@ async def test_relative_time(mock_is_safe, hass: HomeAssistant) -> None: '{{relative_time(strptime("2000-01-01 09:00:00", "%Y-%m-%d %H:%M:%S"))}}' ) with freeze_time(now): - result = template.Template( - relative_time_template, - hass, - ).async_render() + result = render(hass, relative_time_template) assert result == "1 hour" - result = template.Template( + result = render( + hass, ( "{{" " relative_time(" @@ -1690,11 +1445,11 @@ async def test_relative_time(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "2 hours" - result = template.Template( + result = render( + hass, ( "{{" " relative_time(" @@ -1705,14 +1460,14 @@ async def test_relative_time(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 hour" result1 = str( template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") ) - result2 = template.Template( + result2 = render( + hass, ( "{{" " relative_time(" @@ -1723,18 +1478,15 @@ async def test_relative_time(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result1 == result2 - result = template.Template( - '{{relative_time("string")}}', - hass, - ).async_render() + result = render(hass, '{{relative_time("string")}}') assert result == "string" # Test behavior when current time is same as the input time - result = template.Template( + result = render( + hass, ( "{{" " relative_time(" @@ -1745,12 +1497,12 @@ async def test_relative_time(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "0 seconds" # Test behavior when the input time is in the future - result = template.Template( + result = render( + hass, ( "{{" " relative_time(" @@ -1761,11 +1513,10 @@ async def test_relative_time(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "2000-01-01 11:00:00+00:00" - info = template.Template(relative_time_template, hass).async_render_to_info() + info = render_to_info(hass, relative_time_template) assert info.has_time is True @@ -1781,13 +1532,11 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: '{{time_since(strptime("2000-01-01 09:00:00", "%Y-%m-%d %H:%M:%S"))}}' ) with freeze_time(now): - result = template.Template( - time_since_template, - hass, - ).async_render() + result = render(hass, time_since_template) assert result == "1 hour" - result = template.Template( + result = render( + hass, ( "{{" " time_since(" @@ -1798,11 +1547,11 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "2 hours" - result = template.Template( + result = render( + hass, ( "{{" " time_since(" @@ -1813,14 +1562,14 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 hour" result1 = str( template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") ) - result2 = template.Template( + result2 = render( + hass, ( "{{" " time_since(" @@ -1831,11 +1580,11 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result1 == result2 - result = template.Template( + result = render( + hass, ( "{{" " time_since(" @@ -1846,11 +1595,11 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 hour 55 minutes" - result = template.Template( + result = render( + hass, ( "{{" " time_since(" @@ -1861,10 +1610,10 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 hour 54 minutes 33 seconds" - result = template.Template( + result = render( + hass, ( "{{" " time_since(" @@ -1874,10 +1623,10 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "2 hours" - result = template.Template( + result = render( + hass, ( "{{" " time_since(" @@ -1888,10 +1637,10 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "11 months 4 days 1 hour 54 minutes 33 seconds" - result = template.Template( + result = render( + hass, ( "{{" " time_since(" @@ -1901,13 +1650,13 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "11 months" result1 = str( template.strptime("2000-01-01 11:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") ) - result2 = template.Template( + result2 = render( + hass, ( "{{" " time_since(" @@ -1918,17 +1667,13 @@ async def test_time_since(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result1 == result2 - result = template.Template( - '{{time_since("string")}}', - hass, - ).async_render() + result = render(hass, '{{time_since("string")}}') assert result == "string" - info = template.Template(time_since_template, hass).async_render_to_info() + info = render_to_info(hass, time_since_template) assert info.has_time is True @@ -1944,13 +1689,11 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: '{{time_until(strptime("2000-01-01 11:00:00", "%Y-%m-%d %H:%M:%S"))}}' ) with freeze_time(now): - result = template.Template( - time_until_template, - hass, - ).async_render() + result = render(hass, time_until_template) assert result == "1 hour" - result = template.Template( + result = render( + hass, ( "{{" " time_until(" @@ -1961,11 +1704,11 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "2 hours" - result = template.Template( + result = render( + hass, ( "{{" " time_until(" @@ -1976,14 +1719,14 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 hour" result1 = str( template.strptime("2000-01-01 09:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") ) - result2 = template.Template( + result2 = render( + hass, ( "{{" " time_until(" @@ -1994,11 +1737,11 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result1 == result2 - result = template.Template( + result = render( + hass, ( "{{" " time_until(" @@ -2009,11 +1752,11 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 hour 5 minutes" - result = template.Template( + result = render( + hass, ( "{{" " time_until(" @@ -2024,10 +1767,10 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 hour 54 minutes 33 seconds" - result = template.Template( + result = render( + hass, ( "{{" " time_until(" @@ -2037,10 +1780,10 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "2 hours" - result = template.Template( + result = render( + hass, ( "{{" " time_until(" @@ -2051,10 +1794,10 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 year 1 month 2 days 1 hour 54 minutes 33 seconds" - result = template.Template( + result = render( + hass, ( "{{" " time_until(" @@ -2065,13 +1808,13 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result == "1 year 1 month 2 days 2 hours" result1 = str( template.strptime("2000-01-01 09:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") ) - result2 = template.Template( + result2 = render( + hass, ( "{{" " time_until(" @@ -2082,17 +1825,13 @@ async def test_time_until(mock_is_safe, hass: HomeAssistant) -> None: " )" "}}" ), - hass, - ).async_render() + ) assert result1 == result2 - result = template.Template( - '{{time_until("string")}}', - hass, - ).async_render() + result = render(hass, '{{time_until("string")}}') assert result == "string" - info = template.Template(time_until_template, hass).async_render_to_info() + info = render_to_info(hass, time_until_template) assert info.has_time is True @@ -2104,126 +1843,59 @@ def test_timedelta(mock_is_safe, hass: HomeAssistant) -> None: """Test relative_time method.""" now = datetime.strptime("2000-01-01 10:00:00 +00:00", "%Y-%m-%d %H:%M:%S %z") with freeze_time(now): - result = template.Template( - "{{timedelta(seconds=120)}}", - hass, - ).async_render() + result = render(hass, "{{timedelta(seconds=120)}}") assert result == "0:02:00" - result = template.Template( - "{{timedelta(seconds=86400)}}", - hass, - ).async_render() + result = render(hass, "{{timedelta(seconds=86400)}}") assert result == "1 day, 0:00:00" - result = template.Template( - "{{timedelta(days=1, hours=4)}}", hass - ).async_render() + result = render(hass, "{{timedelta(days=1, hours=4)}}") assert result == "1 day, 4:00:00" - result = template.Template( - "{{relative_time(now() - timedelta(seconds=3600))}}", - hass, - ).async_render() + result = render(hass, "{{relative_time(now() - timedelta(seconds=3600))}}") assert result == "1 hour" - result = template.Template( - "{{relative_time(now() - timedelta(seconds=86400))}}", - hass, - ).async_render() + result = render(hass, "{{relative_time(now() - timedelta(seconds=86400))}}") assert result == "1 day" - result = template.Template( - "{{relative_time(now() - timedelta(seconds=86401))}}", - hass, - ).async_render() + result = render(hass, "{{relative_time(now() - timedelta(seconds=86401))}}") assert result == "1 day" - result = template.Template( - "{{relative_time(now() - timedelta(weeks=2, days=1))}}", - hass, - ).async_render() + result = render(hass, "{{relative_time(now() - timedelta(weeks=2, days=1))}}") assert result == "15 days" def test_version(hass: HomeAssistant) -> None: """Test version filter and function.""" - filter_result = template.Template( - "{{ '2099.9.9' | version}}", - hass, - ).async_render() - function_result = template.Template( - "{{ version('2099.9.9')}}", - hass, - ).async_render() + filter_result = render(hass, "{{ '2099.9.9' | version}}") + function_result = render(hass, "{{ version('2099.9.9')}}") assert filter_result == function_result == "2099.9.9" - filter_result = template.Template( - "{{ '2099.9.9' | version < '2099.9.10' }}", - hass, - ).async_render() - function_result = template.Template( - "{{ version('2099.9.9') < '2099.9.10' }}", - hass, - ).async_render() + filter_result = render(hass, "{{ '2099.9.9' | version < '2099.9.10' }}") + function_result = render(hass, "{{ version('2099.9.9') < '2099.9.10' }}") assert filter_result is function_result is True - filter_result = template.Template( - "{{ '2099.9.9' | version == '2099.9.9' }}", - hass, - ).async_render() - function_result = template.Template( - "{{ version('2099.9.9') == '2099.9.9' }}", - hass, - ).async_render() + filter_result = render(hass, "{{ '2099.9.9' | version == '2099.9.9' }}") + function_result = render(hass, "{{ version('2099.9.9') == '2099.9.9' }}") assert filter_result is function_result is True with pytest.raises(TemplateError): - template.Template( - "{{ version(None) < '2099.9.10' }}", - hass, - ).async_render() + render(hass, "{{ version(None) < '2099.9.10' }}") def test_pack(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: """Test struct pack method.""" # render as filter - tpl = template.Template( - """ -{{ value | pack('>I') }} - """, - hass, - ) - variables = { - "value": 0xDEADBEEF, - } - assert tpl.async_render(variables=variables) == b"\xde\xad\xbe\xef" + variables = {"value": 0xDEADBEEF} + assert render(hass, "{{ value | pack('>I') }}", variables) == b"\xde\xad\xbe\xef" # render as function - tpl = template.Template( - """ -{{ pack(value, '>I') }} - """, - hass, - ) - variables = { - "value": 0xDEADBEEF, - } - assert tpl.async_render(variables=variables) == b"\xde\xad\xbe\xef" + assert render(hass, "{{ pack(value, '>I') }}", variables) == b"\xde\xad\xbe\xef" # test with None value - tpl = template.Template( - """ -{{ pack(value, '>I') }} - """, - hass, - ) - variables = { - "value": None, - } # "Template warning: 'pack' unable to pack object with type '%s' and format_string '%s' see https://docs.python.org/3/library/struct.html for more information" - assert tpl.async_render(variables=variables) is None + assert render(hass, "{{ pack(value, '>I') }}", {"value": None}) is None assert ( "Template warning: 'pack' unable to pack object 'None' with type 'NoneType' and" " format_string '>I' see https://docs.python.org/3/library/struct.html for more" @@ -2231,17 +1903,8 @@ def test_pack(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: ) # test with invalid filter - tpl = template.Template( - """ -{{ pack(value, 'invalid filter') }} - """, - hass, - ) - variables = { - "value": 0xDEADBEEF, - } # "Template warning: 'pack' unable to pack object with type '%s' and format_string '%s' see https://docs.python.org/3/library/struct.html for more information" - assert tpl.async_render(variables=variables) is None + assert render(hass, "{{ pack(value, 'invalid filter') }}", variables) is None assert ( "Template warning: 'pack' unable to pack object '3735928559' with type 'int'" " and format_string 'invalid filter' see" @@ -2253,53 +1916,22 @@ def test_pack(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: def test_unpack(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: """Test struct unpack method.""" + variables = {"value": b"\xde\xad\xbe\xef"} + # render as filter - tpl = template.Template( - """ -{{ value | unpack('>I') }} - """, - hass, - ) - variables = { - "value": b"\xde\xad\xbe\xef", - } - assert tpl.async_render(variables=variables) == 0xDEADBEEF + result = render(hass, """{{ value | unpack('>I') }}""", variables) + assert result == 0xDEADBEEF # render as function - tpl = template.Template( - """ -{{ unpack(value, '>I') }} - """, - hass, - ) - variables = { - "value": b"\xde\xad\xbe\xef", - } - assert tpl.async_render(variables=variables) == 0xDEADBEEF + result = render(hass, """{{ unpack(value, '>I') }}""", variables) + assert result == 0xDEADBEEF # unpack with offset - tpl = template.Template( - """ -{{ unpack(value, '>H', offset=2) }} - """, - hass, - ) - variables = { - "value": b"\xde\xad\xbe\xef", - } - assert tpl.async_render(variables=variables) == 0xBEEF + result = render(hass, """{{ unpack(value, '>H', offset=2) }}""", variables) + assert result == 0xBEEF # test with an empty bytes object - tpl = template.Template( - """ -{{ unpack(value, '>I') }} - """, - hass, - ) - variables = { - "value": b"", - } - assert tpl.async_render(variables=variables) is None + assert render(hass, """{{ unpack(value, '>I') }}""", {"value": b""}) is None assert ( "Template warning: 'unpack' unable to unpack object 'b''' with format_string" " '>I' and offset 0 see https://docs.python.org/3/library/struct.html for more" @@ -2307,16 +1939,10 @@ def test_unpack(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: ) # test with invalid filter - tpl = template.Template( - """ -{{ unpack(value, 'invalid filter') }} - """, - hass, + assert ( + render(hass, """{{ unpack(value, 'invalid filter') }}""", {"value": b""}) + is None ) - variables = { - "value": b"", - } - assert tpl.async_render(variables=variables) is None assert ( "Template warning: 'unpack' unable to unpack object 'b''' with format_string" " 'invalid filter' and offset 0 see" @@ -2331,8 +1957,9 @@ def test_distance_function_with_1_state(hass: HomeAssistant) -> None: hass.states.async_set( "test.object", "happy", {"latitude": 32.87336, "longitude": -117.22943} ) - tpl = template.Template("{{ distance(states.test.object) | round }}", hass) - assert tpl.async_render() == 187 + + result = render(hass, "{{ distance(states.test.object) | round }}") + assert result == 187 def test_distance_function_with_2_states(hass: HomeAssistant) -> None: @@ -2346,29 +1973,26 @@ def test_distance_function_with_2_states(hass: HomeAssistant) -> None: "happy", {"latitude": hass.config.latitude, "longitude": hass.config.longitude}, ) - tpl = template.Template( - "{{ distance(states.test.object, states.test.object_2) | round }}", hass + + result = render( + hass, "{{ distance(states.test.object, states.test.object_2) | round }}" ) - assert tpl.async_render() == 187 + assert result == 187 def test_distance_function_with_1_coord(hass: HomeAssistant) -> None: """Test distance function with 1 coord.""" _set_up_units(hass) - tpl = template.Template('{{ distance("32.87336", "-117.22943") | round }}', hass) - assert tpl.async_render() == 187 + + result = render(hass, '{{ distance("32.87336", "-117.22943") | round }}') + assert result == 187 def test_distance_function_with_2_coords(hass: HomeAssistant) -> None: """Test distance function with 2 coords.""" _set_up_units(hass) - assert ( - template.Template( - f'{{{{ distance("32.87336", "-117.22943", {hass.config.latitude}, {hass.config.longitude}) | round }}}}', - hass, - ).async_render() - == 187 - ) + tpl = f'{{{{ distance("32.87336", "-117.22943", {hass.config.latitude}, {hass.config.longitude}) | round }}}}' + assert render(hass, tpl) == 187 def test_distance_function_with_1_state_1_coord(hass: HomeAssistant) -> None: @@ -2379,42 +2003,39 @@ def test_distance_function_with_1_state_1_coord(hass: HomeAssistant) -> None: "happy", {"latitude": hass.config.latitude, "longitude": hass.config.longitude}, ) - tpl = template.Template( - '{{ distance("32.87336", "-117.22943", states.test.object_2) | round }}', - hass, - ) - assert tpl.async_render() == 187 - tpl2 = template.Template( - '{{ distance(states.test.object_2, "32.87336", "-117.22943") | round }}', - hass, + result = render( + hass, '{{ distance("32.87336", "-117.22943", states.test.object_2) | round }}' ) - assert tpl2.async_render() == 187 + assert result == 187 + + result = render( + hass, '{{ distance(states.test.object_2, "32.87336", "-117.22943") | round }}' + ) + assert result == 187 def test_distance_function_return_none_if_invalid_state(hass: HomeAssistant) -> None: """Test distance function return None if invalid state.""" hass.states.async_set("test.object_2", "happy", {"latitude": 10}) - tpl = template.Template("{{ distance(states.test.object_2) | round }}", hass) with pytest.raises(TemplateError): - tpl.async_render() + render(hass, "{{ distance(states.test.object_2) | round }}") def test_distance_function_return_none_if_invalid_coord(hass: HomeAssistant) -> None: """Test distance function return None if invalid coord.""" - assert ( - template.Template('{{ distance("123", "abc") }}', hass).async_render() is None - ) + assert render(hass, '{{ distance("123", "abc") }}') is None - assert template.Template('{{ distance("123") }}', hass).async_render() is None + assert render(hass, '{{ distance("123") }}') is None hass.states.async_set( "test.object_2", "happy", {"latitude": hass.config.latitude, "longitude": hass.config.longitude}, ) - tpl = template.Template('{{ distance("123", states.test_object_2) }}', hass) - assert tpl.async_render() is None + + result = render(hass, '{{ distance("123", states.test_object_2) }}') + assert result is None def test_distance_function_with_2_entity_ids(hass: HomeAssistant) -> None: @@ -2428,10 +2049,9 @@ def test_distance_function_with_2_entity_ids(hass: HomeAssistant) -> None: "happy", {"latitude": hass.config.latitude, "longitude": hass.config.longitude}, ) - tpl = template.Template( - '{{ distance("test.object", "test.object_2") | round }}', hass - ) - assert tpl.async_render() == 187 + + result = render(hass, '{{ distance("test.object", "test.object_2") | round }}') + assert result == 187 def test_distance_function_with_1_entity_1_coord(hass: HomeAssistant) -> None: @@ -2442,10 +2062,11 @@ def test_distance_function_with_1_entity_1_coord(hass: HomeAssistant) -> None: "happy", {"latitude": hass.config.latitude, "longitude": hass.config.longitude}, ) - tpl = template.Template( - '{{ distance("test.object", "32.87336", "-117.22943") | round }}', hass + + result = render( + hass, '{{ distance("test.object", "32.87336", "-117.22943") | round }}' ) - assert tpl.async_render() == 187 + assert result == 187 def test_closest_function_home_vs_domain(hass: HomeAssistant) -> None: @@ -2466,16 +2087,12 @@ def test_closest_function_home_vs_domain(hass: HomeAssistant) -> None: ) assert ( - template.Template( - "{{ closest(states.test_domain).entity_id }}", hass - ).async_render() + render(hass, "{{ closest(states.test_domain).entity_id }}") == "test_domain.object" ) assert ( - template.Template( - "{{ (states.test_domain | closest).entity_id }}", hass - ).async_render() + render(hass, "{{ (states.test_domain | closest).entity_id }}") == "test_domain.object" ) @@ -2497,14 +2114,10 @@ def test_closest_function_home_vs_all_states(hass: HomeAssistant) -> None: {"latitude": hass.config.latitude, "longitude": hass.config.longitude}, ) - assert ( - template.Template("{{ closest(states).entity_id }}", hass).async_render() - == "test_domain_2.and_closer" - ) + assert render(hass, "{{ closest(states).entity_id }}") == "test_domain_2.and_closer" assert ( - template.Template("{{ (states | closest).entity_id }}", hass).async_render() - == "test_domain_2.and_closer" + render(hass, "{{ (states | closest).entity_id }}") == "test_domain_2.and_closer" ) @@ -3204,30 +2817,30 @@ async def test_config_entry_attr(hass: HomeAssistant) -> None: info["state"] = config_entries.ConfigEntryState.NOT_LOADED for key, value in info.items(): - tpl = template.Template( - "{{ config_entry_attr('" + config_entry.entry_id + "', '" + key + "') }}", + assert render( hass, - ) - assert tpl.async_render(parse_result=False) == str(value) + "{{ config_entry_attr('" + config_entry.entry_id + "', '" + key + "') }}", + parse_result=False, + ) == str(value) for config_entry_id, key in ( (config_entry.entry_id, "invalid_key"), (56, "domain"), ): with pytest.raises(TemplateError): - template.Template( + render( + hass, "{{ config_entry_attr(" + json.dumps(config_entry_id) + ", '" + key + "') }}", - hass, - ).async_render() + ) assert ( - template.Template( - "{{ config_entry_attr('invalid_id', 'domain') }}", hass - ).async_render(parse_result=False) + render( + hass, "{{ config_entry_attr('invalid_id', 'domain') }}", parse_result=False + ) == "None" ) @@ -3622,19 +3235,17 @@ def test_closest_function_to_coord(hass: HomeAssistant) -> None: }, ) - tpl = template.Template( + result = render( + hass, f'{{{{ closest("{hass.config.latitude + 0.3}", {hass.config.longitude + 0.3}, states.test_domain).entity_id }}}}', - hass, ) + assert result == "test_domain.closest_zone" - assert tpl.async_render() == "test_domain.closest_zone" - - tpl = template.Template( + result = render( + hass, f'{{{{ (states.test_domain | closest("{hass.config.latitude + 0.3}", {hass.config.longitude + 0.3})).entity_id }}}}', - hass, ) - - assert tpl.async_render() == "test_domain.closest_zone" + assert result == "test_domain.closest_zone" def test_async_render_to_info_with_branching(hass: HomeAssistant) -> None: @@ -3915,9 +3526,9 @@ def test_closest_function_to_state(hass: HomeAssistant) -> None: ) assert ( - template.Template( - "{{ closest(states.zone.far_away, states.test_domain).entity_id }}", hass - ).async_render() + render( + hass, "{{ closest(states.zone.far_away, states.test_domain).entity_id }}" + ) == "test_domain.closest_zone" ) @@ -3934,12 +3545,7 @@ def test_closest_function_invalid_state(hass: HomeAssistant) -> None: ) for state in ("states.zone.non_existing", '"zone.non_existing"'): - assert ( - template.Template( - f"{{{{ closest({state}, states) }}}}", hass - ).async_render() - is None - ) + assert render(hass, f"{{{{ closest({state}, states) }}}}") is None def test_closest_function_state_with_invalid_location(hass: HomeAssistant) -> None: @@ -3951,10 +3557,7 @@ def test_closest_function_state_with_invalid_location(hass: HomeAssistant) -> No ) assert ( - template.Template( - "{{ closest(states.test_domain.closest_home, states) }}", hass - ).async_render() - is None + render(hass, "{{ closest(states.test_domain.closest_home, states) }}") is None ) @@ -3969,25 +3572,13 @@ def test_closest_function_invalid_coordinates(hass: HomeAssistant) -> None: }, ) - assert ( - template.Template( - '{{ closest("invalid", "coord", states) }}', hass - ).async_render() - is None - ) - assert ( - template.Template( - '{{ states | closest("invalid", "coord") }}', hass - ).async_render() - is None - ) + assert render(hass, '{{ closest("invalid", "coord", states) }}') is None + assert render(hass, '{{ states | closest("invalid", "coord") }}') is None def test_closest_function_no_location_states(hass: HomeAssistant) -> None: """Test closest function without location states.""" - assert ( - template.Template("{{ closest(states).entity_id }}", hass).async_render() == "" - ) + assert render(hass, "{{ closest(states).entity_id }}") == "" def test_generate_filter_iterators(hass: HomeAssistant) -> None: @@ -4054,15 +3645,14 @@ def test_generate_select(hass: HomeAssistant) -> None: |join(",", attribute="entity_id") }} """ - tmp = template.Template(template_str, hass) - info = tmp.async_render_to_info() + info = render_to_info(hass, template_str) assert_result_info(info, "", [], []) assert info.domains_lifecycle == {"sensor"} hass.states.async_set("sensor.test_sensor", "off", {"attr": "value"}) hass.states.async_set("sensor.test_sensor_on", "on") - info = tmp.async_render_to_info() + info = render_to_info(hass, template_str) assert_result_info( info, "sensor.test_sensor", @@ -4074,12 +3664,7 @@ def test_generate_select(hass: HomeAssistant) -> None: async def test_async_render_to_info_in_conditional(hass: HomeAssistant) -> None: """Test extract entities function with none entities stuff.""" - template_str = """ -{{ states("sensor.xyz") == "dog" }} - """ - - tmp = template.Template(template_str, hass) - info = tmp.async_render_to_info() + info = render_to_info(hass, '{{ states("sensor.xyz") == "dog" }}') assert_result_info(info, False, ["sensor.xyz"], []) hass.states.async_set("sensor.xyz", "dog") @@ -4094,8 +3679,7 @@ async def test_async_render_to_info_in_conditional(hass: HomeAssistant) -> None: {% endif %} """ - tmp = template.Template(template_str, hass) - info = tmp.async_render_to_info() + info = render_to_info(hass, template_str) assert_result_info(info, True, ["sensor.xyz", "sensor.cow"], []) hass.states.async_set("sensor.xyz", "sheep") @@ -4103,8 +3687,7 @@ async def test_async_render_to_info_in_conditional(hass: HomeAssistant) -> None: await hass.async_block_till_done() - tmp = template.Template(template_str, hass) - info = tmp.async_render_to_info() + info = render_to_info(hass, template_str) assert_result_info(info, "oink", ["sensor.xyz", "sensor.pig"], []) @@ -4131,23 +3714,19 @@ def test_state_with_unit(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test", "23", {ATTR_UNIT_OF_MEASUREMENT: "beers"}) hass.states.async_set("sensor.test2", "wow") - tpl = template.Template("{{ states.sensor.test.state_with_unit }}", hass) + result = render(hass, "{{ states.sensor.test.state_with_unit }}") + assert result == "23 beers" - assert tpl.async_render() == "23 beers" + result = render(hass, "{{ states.sensor.test2.state_with_unit }}") + assert result == "wow" - tpl = template.Template("{{ states.sensor.test2.state_with_unit }}", hass) - - assert tpl.async_render() == "wow" - - tpl = template.Template( - "{% for state in states %}{{ state.state_with_unit }} {% endfor %}", hass + result = render( + hass, "{% for state in states %}{{ state.state_with_unit }} {% endfor %}" ) + assert result == "23 beers wow" - assert tpl.async_render() == "23 beers wow" - - tpl = template.Template("{{ states.sensor.non_existing.state_with_unit }}", hass) - - assert tpl.async_render() == "" + result = render(hass, "{{ states.sensor.non_existing.state_with_unit }}") + assert result == "" def test_state_with_unit_and_rounding( @@ -4273,11 +3852,11 @@ def test_length_of_states(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test2", "wow") hass.states.async_set("climate.test2", "cooling") - tpl = template.Template("{{ states | length }}", hass) - assert tpl.async_render() == 3 + result = render(hass, "{{ states | length }}") + assert result == 3 - tpl = template.Template("{{ states.sensor | length }}", hass) - assert tpl.async_render() == 2 + result = render(hass, "{{ states.sensor | length }}") + assert result == 2 def test_render_complex_handling_non_template_values(hass: HomeAssistant) -> None: @@ -4289,46 +3868,48 @@ def test_render_complex_handling_non_template_values(hass: HomeAssistant) -> Non def test_as_timedelta(hass: HomeAssistant) -> None: """Test the as_timedelta function/filter.""" - tpl = template.Template("{{ as_timedelta('PT10M') }}", hass) - assert tpl.async_render() == "0:10:00" - tpl = template.Template("{{ 'PT10M' | as_timedelta }}", hass) - assert tpl.async_render() == "0:10:00" + result = render(hass, "{{ as_timedelta('PT10M') }}") + assert result == "0:10:00" - tpl = template.Template("{{ 'T10M' | as_timedelta }}", hass) - assert tpl.async_render() is None + result = render(hass, "{{ 'PT10M' | as_timedelta }}") + assert result == "0:10:00" + + result = render(hass, "{{ 'T10M' | as_timedelta }}") + assert result is None def test_iif(hass: HomeAssistant) -> None: """Test the immediate if function/filter.""" - tpl = template.Template("{{ (1 == 1) | iif }}", hass) - assert tpl.async_render() is True - tpl = template.Template("{{ (1 == 2) | iif }}", hass) - assert tpl.async_render() is False + result = render(hass, "{{ (1 == 1) | iif }}") + assert result is True - tpl = template.Template("{{ (1 == 1) | iif('yes') }}", hass) - assert tpl.async_render() == "yes" + result = render(hass, "{{ (1 == 2) | iif }}") + assert result is False - tpl = template.Template("{{ (1 == 2) | iif('yes') }}", hass) - assert tpl.async_render() is False + result = render(hass, "{{ (1 == 1) | iif('yes') }}") + assert result == "yes" - tpl = template.Template("{{ (1 == 2) | iif('yes', 'no') }}", hass) - assert tpl.async_render() == "no" + result = render(hass, "{{ (1 == 2) | iif('yes') }}") + assert result is False - tpl = template.Template("{{ not_exists | default(None) | iif('yes', 'no') }}", hass) - assert tpl.async_render() == "no" + result = render(hass, "{{ (1 == 2) | iif('yes', 'no') }}") + assert result == "no" - tpl = template.Template( - "{{ not_exists | default(None) | iif('yes', 'no', 'unknown') }}", hass + result = render(hass, "{{ not_exists | default(None) | iif('yes', 'no') }}") + assert result == "no" + + result = render( + hass, "{{ not_exists | default(None) | iif('yes', 'no', 'unknown') }}" ) - assert tpl.async_render() == "unknown" + assert result == "unknown" - tpl = template.Template("{{ iif(1 == 1) }}", hass) - assert tpl.async_render() is True + result = render(hass, "{{ iif(1 == 1) }}") + assert result is True - tpl = template.Template("{{ iif(1 == 2, 'yes', 'no') }}", hass) - assert tpl.async_render() == "no" + result = render(hass, "{{ iif(1 == 2, 'yes', 'no') }}") + assert result == "no" @pytest.mark.usefixtures("hass") @@ -4366,17 +3947,14 @@ def test_is_template_string() -> None: async def test_protected_blocked(hass: HomeAssistant) -> None: """Test accessing __getattr__ produces a template error.""" - tmp = template.Template('{{ states.__getattr__("any") }}', hass) with pytest.raises(TemplateError): - tmp.async_render() + render(hass, '{{ states.__getattr__("any") }}') - tmp = template.Template('{{ states.sensor.__getattr__("any") }}', hass) with pytest.raises(TemplateError): - tmp.async_render() + render(hass, '{{ states.sensor.__getattr__("any") }}') - tmp = template.Template('{{ states.sensor.any.__getattr__("any") }}', hass) with pytest.raises(TemplateError): - tmp.async_render() + render(hass, '{{ states.sensor.any.__getattr__("any") }}') async def test_demo_template(hass: HomeAssistant) -> None: @@ -4412,9 +3990,7 @@ For loop example getting 3 entity values: {{ state.name | lower }} is {{state.state_with_unit}} {%- endfor %}. """ - tmp = template.Template(demo_template_str, hass) - - result = tmp.async_render() + result = render(hass, demo_template_str) assert "The temperature is 25" in result assert "is on" in result assert "sensor0" in result @@ -4426,15 +4002,15 @@ async def test_slice_states(hass: HomeAssistant) -> None: """Test iterating states with a slice.""" hass.states.async_set("sensor.test", "23") - tpl = template.Template( + result = render( + hass, ( "{% for states in states | slice(1) -%}{% set state = states | first %}" "{{ state.entity_id }}" "{%- endfor %}" ), - hass, ) - assert tpl.async_render() == "sensor.test" + assert result == "sensor.test" async def test_lifecycle(hass: HomeAssistant) -> None: @@ -4455,9 +4031,7 @@ async def test_lifecycle(hass: HomeAssistant) -> None: await hass.async_block_till_done() - tmp = template.Template("{{ states | count }}", hass) - - info = tmp.async_render_to_info() + info = render_to_info(hass, "{{ states | count }}") assert info.all_states is False assert info.all_states_lifecycle is True assert info.rate_limit is None @@ -4525,8 +4099,7 @@ async def test_lights(hass: HomeAssistant) -> None: states.append(f"light.sensor{i}") hass.states.async_set(f"light.sensor{i}", "on") - tmp = template.Template(tmpl, hass) - info = tmp.async_render_to_info() + info = render_to_info(hass, tmpl) assert info.entities == set() assert info.domains == {"light"} @@ -4539,64 +4112,42 @@ async def test_template_errors(hass: HomeAssistant) -> None: """Test template rendering wraps exceptions with TemplateError.""" with pytest.raises(TemplateError): - template.Template("{{ now() | rando }}", hass).async_render() + render(hass, "{{ now() | rando }}") with pytest.raises(TemplateError): - template.Template("{{ utcnow() | rando }}", hass).async_render() + render(hass, "{{ utcnow() | rando }}") with pytest.raises(TemplateError): - template.Template("{{ now() | random }}", hass).async_render() + render(hass, "{{ now() | random }}") with pytest.raises(TemplateError): - template.Template("{{ utcnow() | random }}", hass).async_render() + render(hass, "{{ utcnow() | random }}") async def test_state_attributes(hass: HomeAssistant) -> None: """Test state attributes.""" hass.states.async_set("sensor.test", "23") - tpl = template.Template( - "{{ states.sensor.test.last_changed }}", - hass, - ) - assert tpl.async_render() == str(hass.states.get("sensor.test").last_changed) + result = render(hass, "{{ states.sensor.test.last_changed }}") + assert result == str(hass.states.get("sensor.test").last_changed) - tpl = template.Template( - "{{ states.sensor.test.object_id }}", - hass, - ) - assert tpl.async_render() == hass.states.get("sensor.test").object_id + result = render(hass, "{{ states.sensor.test.object_id }}") + assert result == hass.states.get("sensor.test").object_id - tpl = template.Template( - "{{ states.sensor.test.domain }}", - hass, - ) - assert tpl.async_render() == hass.states.get("sensor.test").domain + result = render(hass, "{{ states.sensor.test.domain }}") + assert result == hass.states.get("sensor.test").domain - tpl = template.Template( - "{{ states.sensor.test.context.id }}", - hass, - ) - assert tpl.async_render() == hass.states.get("sensor.test").context.id + result = render(hass, "{{ states.sensor.test.context.id }}") + assert result == hass.states.get("sensor.test").context.id - tpl = template.Template( - "{{ states.sensor.test.state_with_unit }}", - hass, - ) - assert tpl.async_render() == 23 + result = render(hass, "{{ states.sensor.test.state_with_unit }}") + assert result == 23 - tpl = template.Template( - "{{ states.sensor.test.invalid_prop }}", - hass, - ) - assert tpl.async_render() == "" + result = render(hass, "{{ states.sensor.test.invalid_prop }}") + assert result == "" - tpl = template.Template( - "{{ states.sensor.test.invalid_prop.xx }}", - hass, - ) with pytest.raises(TemplateError): - tpl.async_render() + render(hass, "{{ states.sensor.test.invalid_prop.xx }}") async def test_unavailable_states(hass: HomeAssistant) -> None: @@ -4609,25 +4160,25 @@ async def test_unavailable_states(hass: HomeAssistant) -> None: hass.states.async_set("light.unknown", "unknown") hass.states.async_set("light.none", "none") - tpl = template.Template( + result = render( + hass, ( "{{ states | selectattr('state', 'in', ['unavailable','unknown','none']) " "| sort(attribute='entity_id') | map(attribute='entity_id') | list | join(', ') }}" ), - hass, ) - assert tpl.async_render() == "light.none, light.unavailable, light.unknown" + assert result == "light.none, light.unavailable, light.unknown" - tpl = template.Template( + result = render( + hass, ( "{{ states.light " "| selectattr('state', 'in', ['unavailable','unknown','none']) " "| sort(attribute='entity_id') | map(attribute='entity_id') | list " "| join(', ') }}" ), - hass, ) - assert tpl.async_render() == "light.none, light.unavailable, light.unknown" + assert result == "light.none, light.unavailable, light.unknown" async def test_no_result_parsing(hass: HomeAssistant) -> None: @@ -4635,21 +4186,13 @@ async def test_no_result_parsing(hass: HomeAssistant) -> None: hass.states.async_set("sensor.temperature", "12") assert ( - template.Template("{{ states.sensor.temperature.state }}", hass).async_render( - parse_result=False - ) + render(hass, "{{ states.sensor.temperature.state }}", parse_result=False) == "12" ) - assert ( - template.Template("{{ false }}", hass).async_render(parse_result=False) - == "False" - ) + assert render(hass, "{{ false }}", parse_result=False) == "False" - assert ( - template.Template("{{ [1, 2, 3] }}", hass).async_render(parse_result=False) - == "[1, 2, 3]" - ) + assert render(hass, "{{ [1, 2, 3] }}", parse_result=False) == "[1, 2, 3]" async def test_is_static_still_ast_evals(hass: HomeAssistant) -> None: @@ -4667,8 +4210,7 @@ async def test_result_wrappers(hass: HomeAssistant) -> None: ("(1, 2)", (1, 2), tuple, vol.ExactSequence([int, int])), ('{"hello": True}', {"hello": True}, dict, vol.Schema({"hello": bool})), ): - tpl = template.Template(text, hass) - result = tpl.async_render() + result = render(hass, text) assert isinstance(result, orig_type) assert isinstance(result, template.ResultWrapper) assert result == native @@ -4706,7 +4248,7 @@ async def test_parse_result(hass: HomeAssistant) -> None: ("010", "010"), ("0011101.00100001010001", "0011101.00100001010001"), ): - assert template.Template(tpl, hass).async_render() == result + assert render(hass, tpl) == result @pytest.mark.parametrize( @@ -4723,11 +4265,11 @@ async def test_undefined_symbol_warnings( template_string: str, ) -> None: """Test a warning is logged on undefined variables.""" - tpl = template.Template(template_string, hass) - assert tpl.async_render() == "" + + assert render(hass, template_string) == "" assert ( - "Template variable warning: 'no_such_variable' is undefined when rendering " - f"'{template_string}'" in caplog.text + f"Template variable warning: 'no_such_variable' is undefined when rendering '{template_string}'" + in caplog.text ) @@ -4766,15 +4308,11 @@ async def test_template_states_can_serialize(hass: HomeAssistant) -> None: def test_contains(hass: HomeAssistant, seq, value, expected) -> None: """Test contains.""" assert ( - template.Template("{{ seq | contains(value) }}", hass).async_render( - {"seq": seq, "value": value} - ) + render(hass, "{{ seq | contains(value) }}", {"seq": seq, "value": value}) == expected ) assert ( - template.Template("{{ seq is contains(value) }}", hass).async_render( - {"seq": seq, "value": value} - ) + render(hass, "{{ seq is contains(value) }}", {"seq": seq, "value": value}) == expected ) @@ -5529,9 +5067,8 @@ async def test_template_thread_safety_checks(hass: HomeAssistant) -> None: def test_template_output_exceeds_maximum_size(hass: HomeAssistant) -> None: """Test template output exceeds maximum size.""" - tpl = template.Template("{{ 'a' * 1024 * 257 }}", hass) with pytest.raises(TemplateError): - tpl.async_render() + render(hass, "{{ 'a' * 1024 * 257 }}") @pytest.mark.parametrize( @@ -5691,9 +5228,11 @@ async def test_merge_response( _template = "{{ merge_response(" + str(service_response) + ") }}" - tpl = template.Template(_template, hass) assert service_response == snapshot(name="a_response") - assert tpl.async_render() == snapshot(name="b_rendered") + assert render( + hass, + _template, + ) == snapshot(name="b_rendered") async def test_merge_response_with_entity_id_in_response( @@ -5711,7 +5250,7 @@ async def test_merge_response_with_entity_id_in_response( TemplateError, match="ValueError: Response dictionary already contains key 'entity_id'", ): - template.Template(_template, hass).async_render() + render(hass, _template) service_response = { "test.response": { @@ -5730,7 +5269,7 @@ async def test_merge_response_with_entity_id_in_response( TemplateError, match="ValueError: Response dictionary already contains key 'entity_id'", ): - template.Template(_template, hass).async_render() + render(hass, _template) async def test_merge_response_with_empty_response( @@ -5745,9 +5284,8 @@ async def test_merge_response_with_empty_response( "calendar.yap_house_schedules": {"events": []}, } _template = "{{ merge_response(" + str(service_response) + ") }}" - tpl = template.Template(_template, hass) assert service_response == snapshot(name="a_response") - assert tpl.async_render() == snapshot(name="b_rendered") + assert render(hass, _template) == snapshot(name="b_rendered") async def test_response_empty_dict( @@ -5758,8 +5296,9 @@ async def test_response_empty_dict( service_response = {} _template = "{{ merge_response(" + str(service_response) + ") }}" - tpl = template.Template(_template, hass) - assert tpl.async_render() == [] + + result = render(hass, _template) + assert result == [] async def test_response_incorrect_value( @@ -5771,7 +5310,7 @@ async def test_response_incorrect_value( service_response = "incorrect" _template = "{{ merge_response(" + str(service_response) + ") }}" with pytest.raises(TemplateError, match="TypeError: Response is not a dictionary"): - template.Template(_template, hass).async_render() + render(hass, _template) async def test_merge_response_with_incorrect_response(hass: HomeAssistant) -> None: @@ -5779,17 +5318,15 @@ async def test_merge_response_with_incorrect_response(hass: HomeAssistant) -> No service_response = {"calendar.sports": []} _template = "{{ merge_response(" + str(service_response) + ") }}" - tpl = template.Template(_template, hass) with pytest.raises(TemplateError, match="TypeError: Response is not a dictionary"): - tpl.async_render() + render(hass, _template) service_response = { "binary_sensor.workday": [], } _template = "{{ merge_response(" + str(service_response) + ") }}" - tpl = template.Template(_template, hass) with pytest.raises(TemplateError, match="TypeError: Response is not a dictionary"): - tpl.async_render() + render(hass, _template) def test_warn_no_hass(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: @@ -5823,68 +5360,65 @@ async def test_merge_response_not_mutate_original_object( "{{ merge_response(calendar_response) }}" ) - tpl = template.Template(_template, hass) - assert tpl.async_render() + assert render(hass, _template) def test_typeof(hass: HomeAssistant) -> None: """Test the typeof debug filter/function.""" - assert template.Template("{{ True | typeof }}", hass).async_render() == "bool" - assert template.Template("{{ typeof(True) }}", hass).async_render() == "bool" + assert render(hass, "{{ True | typeof }}") == "bool" + assert render(hass, "{{ typeof(True) }}") == "bool" - assert template.Template("{{ [1, 2, 3] | typeof }}", hass).async_render() == "list" - assert template.Template("{{ typeof([1, 2, 3]) }}", hass).async_render() == "list" + assert render(hass, "{{ [1, 2, 3] | typeof }}") == "list" + assert render(hass, "{{ typeof([1, 2, 3]) }}") == "list" - assert template.Template("{{ 1 | typeof }}", hass).async_render() == "int" - assert template.Template("{{ typeof(1) }}", hass).async_render() == "int" + assert render(hass, "{{ 1 | typeof }}") == "int" + assert render(hass, "{{ typeof(1) }}") == "int" - assert template.Template("{{ 1.1 | typeof }}", hass).async_render() == "float" - assert template.Template("{{ typeof(1.1) }}", hass).async_render() == "float" + assert render(hass, "{{ 1.1 | typeof }}") == "float" + assert render(hass, "{{ typeof(1.1) }}") == "float" - assert template.Template("{{ None | typeof }}", hass).async_render() == "NoneType" - assert template.Template("{{ typeof(None) }}", hass).async_render() == "NoneType" + assert render(hass, "{{ None | typeof }}") == "NoneType" + assert render(hass, "{{ typeof(None) }}") == "NoneType" - assert ( - template.Template("{{ 'Home Assistant' | typeof }}", hass).async_render() - == "str" - ) - assert ( - template.Template("{{ typeof('Home Assistant') }}", hass).async_render() - == "str" - ) + assert render(hass, "{{ 'Home Assistant' | typeof }}") == "str" + assert render(hass, "{{ typeof('Home Assistant') }}") == "str" def test_combine(hass: HomeAssistant) -> None: """Test combine filter and function.""" - assert template.Template( - "{{ {'a': 1, 'b': 2} | combine({'b': 3, 'c': 4}) }}", hass - ).async_render() == {"a": 1, "b": 3, "c": 4} + assert render(hass, "{{ {'a': 1, 'b': 2} | combine({'b': 3, 'c': 4}) }}") == { + "a": 1, + "b": 3, + "c": 4, + } - assert template.Template( - "{{ combine({'a': 1, 'b': 2}, {'b': 3, 'c': 4}) }}", hass - ).async_render() == {"a": 1, "b": 3, "c": 4} + assert render(hass, "{{ combine({'a': 1, 'b': 2}, {'b': 3, 'c': 4}) }}") == { + "a": 1, + "b": 3, + "c": 4, + } - assert template.Template( - "{{ combine({'a': 1, 'b': {'x': 1}}, {'b': {'y': 2}, 'c': 4}, recursive=True) }}", + assert render( hass, - ).async_render() == {"a": 1, "b": {"x": 1, "y": 2}, "c": 4} + "{{ combine({'a': 1, 'b': {'x': 1}}, {'b': {'y': 2}, 'c': 4}, recursive=True) }}", + ) == {"a": 1, "b": {"x": 1, "y": 2}, "c": 4} # Test that recursive=False does not merge nested dictionaries - assert template.Template( - "{{ combine({'a': 1, 'b': {'x': 1}}, {'b': {'y': 2}, 'c': 4}, recursive=False) }}", + assert render( hass, - ).async_render() == {"a": 1, "b": {"y": 2}, "c": 4} + "{{ combine({'a': 1, 'b': {'x': 1}}, {'b': {'y': 2}, 'c': 4}, recursive=False) }}", + ) == {"a": 1, "b": {"y": 2}, "c": 4} # Test that None values are handled correctly in recursive merge - assert template.Template( - "{{ combine({'a': 1, 'b': none}, {'b': {'y': 2}, 'c': 4}, recursive=True) }}", + assert render( hass, - ).async_render() == {"a": 1, "b": {"y": 2}, "c": 4} + "{{ combine({'a': 1, 'b': none}, {'b': {'y': 2}, 'c': 4}, recursive=True) }}", + ) == {"a": 1, "b": {"y": 2}, "c": 4} with pytest.raises( TemplateError, match="combine expected at least 1 argument, got 0" ): - template.Template("{{ combine() }}", hass).async_render() + render(hass, "{{ combine() }}") with pytest.raises(TemplateError, match="combine expected a dict, got str"): - template.Template("{{ {'a': 1} | combine('not a dict') }}", hass).async_render() + render(hass, "{{ {'a': 1} | combine('not a dict') }}") diff --git a/tests/helpers/template/test_render_info.py b/tests/helpers/template/test_render_info.py index 9b746a84610..1f638ec94e5 100644 --- a/tests/helpers/template/test_render_info.py +++ b/tests/helpers/template/test_render_info.py @@ -17,9 +17,14 @@ from homeassistant.helpers.template.render_info import ( ) -def test_render_info_initialization(hass: HomeAssistant) -> None: +@pytest.fixture +def template_obj(hass: HomeAssistant) -> template.Template: + """Template object for test_render_info.""" + return template.Template("{{ 1 + 1 }}", hass) + + +def test_render_info_initialization(template_obj: template.Template) -> None: """Test RenderInfo initialization.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) assert info.template is template_obj @@ -37,9 +42,8 @@ def test_render_info_initialization(hass: HomeAssistant) -> None: assert info.filter is _true -def test_render_info_repr(hass: HomeAssistant) -> None: +def test_render_info_repr(template_obj: template.Template) -> None: """Test RenderInfo representation.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) info.domains.add("sensor") info.entities.add("sensor.test") @@ -50,9 +54,8 @@ def test_render_info_repr(hass: HomeAssistant) -> None: assert "entities={'sensor.test'}" in repr_str -def test_render_info_result(hass: HomeAssistant) -> None: +def test_render_info_result(template_obj: template.Template) -> None: """Test RenderInfo result property.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) # Test with no result set - should return None cast as str @@ -68,9 +71,10 @@ def test_render_info_result(hass: HomeAssistant) -> None: info.result() -def test_render_info_filter_domains_and_entities(hass: HomeAssistant) -> None: +def test_render_info_filter_domains_and_entities( + template_obj: template.Template, +) -> None: """Test RenderInfo entity and domain filtering.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) # Add domain and entity @@ -85,9 +89,8 @@ def test_render_info_filter_domains_and_entities(hass: HomeAssistant) -> None: assert info._filter_domains_and_entities("switch.kitchen") is False -def test_render_info_filter_entities(hass: HomeAssistant) -> None: +def test_render_info_filter_entities(template_obj: template.Template) -> None: """Test RenderInfo entity-only filtering.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) info.entities.add("sensor.test") @@ -96,9 +99,8 @@ def test_render_info_filter_entities(hass: HomeAssistant) -> None: assert info._filter_entities("sensor.other") is False -def test_render_info_filter_lifecycle_domains(hass: HomeAssistant) -> None: +def test_render_info_filter_lifecycle_domains(template_obj: template.Template) -> None: """Test RenderInfo domain lifecycle filtering.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) info.domains_lifecycle.add("sensor") @@ -107,9 +109,8 @@ def test_render_info_filter_lifecycle_domains(hass: HomeAssistant) -> None: assert info._filter_lifecycle_domains("light.test") is False -def test_render_info_freeze_static(hass: HomeAssistant) -> None: +def test_render_info_freeze_static(template_obj: template.Template) -> None: """Test RenderInfo static freezing.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) info.domains.add("sensor") @@ -124,9 +125,8 @@ def test_render_info_freeze_static(hass: HomeAssistant) -> None: assert isinstance(info.entities, frozenset) -def test_render_info_freeze(hass: HomeAssistant) -> None: +def test_render_info_freeze(template_obj: template.Template) -> None: """Test RenderInfo freezing with rate limits.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) # Test all_states rate limit @@ -147,9 +147,8 @@ def test_render_info_freeze(hass: HomeAssistant) -> None: assert info.rate_limit == ALL_STATES_RATE_LIMIT -def test_render_info_freeze_filters(hass: HomeAssistant) -> None: +def test_render_info_freeze_filters(template_obj: template.Template) -> None: """Test RenderInfo filter assignment during freeze.""" - template_obj = template.Template("{{ 1 + 1 }}", hass) # Test lifecycle filter assignment info = RenderInfo(template_obj) @@ -180,13 +179,12 @@ def test_render_info_freeze_filters(hass: HomeAssistant) -> None: assert info.filter is _false -def test_render_info_context_var(hass: HomeAssistant) -> None: +def test_render_info_context_var(template_obj: template.Template) -> None: """Test render_info_cv context variable.""" # Should start as None assert render_info_cv.get() is None # Test setting and getting - template_obj = template.Template("{{ 1 + 1 }}", hass) info = RenderInfo(template_obj) render_info_cv.set(info) assert render_info_cv.get() is info