From e2f5a707cea0b44adea1bfca555ddda790da865e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 22 Jun 2023 11:30:19 +0200 Subject: [PATCH] Fix use_device_name in case device device class translations are used (#95010) Co-authored-by: Erik --- homeassistant/helpers/entity.py | 4 +- tests/helpers/test_entity.py | 200 ++++++++++++++++++++++++++++++-- 2 files changed, 194 insertions(+), 10 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index b80e244cb8a..97b3485c893 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -355,14 +355,14 @@ class Entity(ABC): if hasattr(self, "entity_description"): if not (name := self.entity_description.name): return True - if name is UNDEFINED: + if name is UNDEFINED and not self._default_to_device_class_name(): # Backwards compatibility with leaving EntityDescription.name unassigned # for device name. # Deprecated in HA Core 2023.6, remove in HA Core 2023.9 report_implicit_device_name() return True return False - if self.name is UNDEFINED: + if self.name is UNDEFINED and not self._default_to_device_class_name(): # Backwards compatibility with not overriding name property for device name. # Deprecated in HA Core 2023.6, remove in HA Core 2023.9 report_implicit_device_name() diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 1168f4b40f8..1861dc54844 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -1,8 +1,10 @@ """Test the entity helper.""" import asyncio +from collections.abc import Iterable import dataclasses from datetime import timedelta import threading +from typing import Any from unittest.mock import MagicMock, PropertyMock, patch import pytest @@ -953,8 +955,6 @@ async def _test_friendly_name( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, ent: entity.Entity, - has_entity_name: bool, - entity_name: str | None, expected_friendly_name: str | None, warn_implicit_name: bool, ) -> None: @@ -1017,8 +1017,6 @@ async def test_friendly_name_attr( hass, caplog, ent, - has_entity_name, - entity_name, expected_friendly_name, warn_implicit_name, ) @@ -1060,13 +1058,78 @@ async def test_friendly_name_description( hass, caplog, ent, - has_entity_name, - entity_name, expected_friendly_name, warn_implicit_name, ) +@pytest.mark.parametrize( + ("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"), + ( + (False, "Entity Blu", "Entity Blu", False), + (False, None, None, False), + (False, UNDEFINED, None, False), + (True, "Entity Blu", "Device Bla Entity Blu", False), + (True, None, "Device Bla", False), + (True, UNDEFINED, "Device Bla English cls", False), + ), +) +async def test_friendly_name_description_device_class_name( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + has_entity_name: bool, + entity_name: str | None, + expected_friendly_name: str | None, + warn_implicit_name: bool, +) -> None: + """Test friendly name when the entity has an entity description.""" + + translations = { + "en": {"component.test_domain.entity_component.test_class.name": "English cls"}, + } + + async def async_get_translations( + hass: HomeAssistant, + language: str, + category: str, + integrations: Iterable[str] | None = None, + config_flow: bool | None = None, + ) -> dict[str, Any]: + """Return all backend translations.""" + return translations[language] + + class DeviceClassNameMockEntity(MockEntity): + def _default_to_device_class_name(self) -> bool: + """Return True if an unnamed entity should be named by its device class.""" + return True + + ent = DeviceClassNameMockEntity( + unique_id="qwer", + device_info={ + "identifiers": {("hue", "1234")}, + "connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")}, + "name": "Device Bla", + }, + ) + ent.entity_description = entity.EntityDescription( + "test", + device_class="test_class", + has_entity_name=has_entity_name, + name=entity_name, + ) + with patch( + "homeassistant.helpers.entity_platform.translation.async_get_translations", + side_effect=async_get_translations, + ): + await _test_friendly_name( + hass, + caplog, + ent, + expected_friendly_name, + warn_implicit_name, + ) + + @pytest.mark.parametrize( ("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"), ( @@ -1102,13 +1165,134 @@ async def test_friendly_name_property( hass, caplog, ent, - has_entity_name, - entity_name, expected_friendly_name, warn_implicit_name, ) +@pytest.mark.parametrize( + ("has_entity_name", "entity_name", "expected_friendly_name", "warn_implicit_name"), + ( + (False, "Entity Blu", "Entity Blu", False), + (False, None, None, False), + (False, UNDEFINED, None, False), + (True, "Entity Blu", "Device Bla Entity Blu", False), + (True, None, "Device Bla", False), + # Won't use the device class name because the entity overrides the name property + (True, UNDEFINED, "Device Bla None", False), + ), +) +async def test_friendly_name_property_device_class_name( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + has_entity_name: bool, + entity_name: str | None, + expected_friendly_name: str | None, + warn_implicit_name: bool, +) -> None: + """Test friendly name when the entity has overridden the name property.""" + + translations = { + "en": {"component.test_domain.entity_component.test_class.name": "English cls"}, + } + + async def async_get_translations( + hass: HomeAssistant, + language: str, + category: str, + integrations: Iterable[str] | None = None, + config_flow: bool | None = None, + ) -> dict[str, Any]: + """Return all backend translations.""" + return translations[language] + + class DeviceClassNameMockEntity(MockEntity): + def _default_to_device_class_name(self) -> bool: + """Return True if an unnamed entity should be named by its device class.""" + return True + + ent = DeviceClassNameMockEntity( + unique_id="qwer", + device_class="test_class", + device_info={ + "identifiers": {("hue", "1234")}, + "connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")}, + "name": "Device Bla", + }, + has_entity_name=has_entity_name, + name=entity_name, + ) + with patch( + "homeassistant.helpers.entity_platform.translation.async_get_translations", + side_effect=async_get_translations, + ): + await _test_friendly_name( + hass, + caplog, + ent, + expected_friendly_name, + warn_implicit_name, + ) + + +@pytest.mark.parametrize( + ("has_entity_name", "expected_friendly_name", "warn_implicit_name"), + ( + (False, None, False), + (True, "Device Bla English cls", False), + ), +) +async def test_friendly_name_device_class_name( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + has_entity_name: bool, + expected_friendly_name: str | None, + warn_implicit_name: bool, +) -> None: + """Test friendly name when the entity has not set the name in any way.""" + + translations = { + "en": {"component.test_domain.entity_component.test_class.name": "English cls"}, + } + + async def async_get_translations( + hass: HomeAssistant, + language: str, + category: str, + integrations: Iterable[str] | None = None, + config_flow: bool | None = None, + ) -> dict[str, Any]: + """Return all backend translations.""" + return translations[language] + + class DeviceClassNameMockEntity(MockEntity): + def _default_to_device_class_name(self) -> bool: + """Return True if an unnamed entity should be named by its device class.""" + return True + + ent = DeviceClassNameMockEntity( + unique_id="qwer", + device_class="test_class", + device_info={ + "identifiers": {("hue", "1234")}, + "connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")}, + "name": "Device Bla", + }, + has_entity_name=has_entity_name, + ) + with patch( + "homeassistant.helpers.entity_platform.translation.async_get_translations", + side_effect=async_get_translations, + ): + await _test_friendly_name( + hass, + caplog, + ent, + expected_friendly_name, + warn_implicit_name, + ) + + @pytest.mark.parametrize( ( "entity_name",