From 3e18e6c5d99345517810f443f521267894f02cb9 Mon Sep 17 00:00:00 2001 From: "ruohan.chen" Date: Mon, 14 Sep 2026 22:09:57 +0800 Subject: [PATCH] Name the two ZhongHong fan speeds the climate component does not (#182075) --- .../components/zhong_hong/climate.py | 3 + .../components/zhong_hong/strings.json | 14 ++++ tests/components/zhong_hong/test_climate.py | 69 ++++++++++++++++++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py index c31547529a18..adf252e28ec4 100644 --- a/homeassistant/components/zhong_hong/climate.py +++ b/homeassistant/components/zhong_hong/climate.py @@ -186,6 +186,9 @@ class ZhongHongClimate(CoordinatorEntity[ZhongHongCoordinator], ClimateEntity): ) _attr_target_temperature_step = 1 _attr_temperature_unit = UnitOfTemperature.CELSIUS + # Two of the five speeds the gateway addresses have no name of their own in + # the climate component, so they are named here. + _attr_translation_key = "air_conditioner" def __init__( self, diff --git a/homeassistant/components/zhong_hong/strings.json b/homeassistant/components/zhong_hong/strings.json index 4e8646fee241..b67f52c06e20 100644 --- a/homeassistant/components/zhong_hong/strings.json +++ b/homeassistant/components/zhong_hong/strings.json @@ -24,6 +24,20 @@ } } }, + "entity": { + "climate": { + "air_conditioner": { + "state_attributes": { + "fan_mode": { + "state": { + "medium_high": "Medium high", + "medium_low": "Medium low" + } + } + } + } + } + }, "exceptions": { "cannot_connect": { "message": "Failed to connect to the ZhongHong gateway at {host}." diff --git a/tests/components/zhong_hong/test_climate.py b/tests/components/zhong_hong/test_climate.py index d6f6d72ff0cf..45a19de0b4d7 100644 --- a/tests/components/zhong_hong/test_climate.py +++ b/tests/components/zhong_hong/test_climate.py @@ -12,7 +12,9 @@ from homeassistant.components.climate import ( ATTR_FAN_MODES, ATTR_HVAC_MODE, DOMAIN as CLIMATE_DOMAIN, + FAN_HIGH, FAN_LOW, + FAN_MIDDLE, SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, SERVICE_SET_TEMPERATURE, @@ -20,7 +22,12 @@ from homeassistant.components.climate import ( SERVICE_TURN_ON, HVACMode, ) -from homeassistant.components.zhong_hong.const import ALL_FAN_MODES, FAN_MEDIUM_HIGH +from homeassistant.components.zhong_hong.const import ( + ALL_FAN_MODES, + DOMAIN, + FAN_MEDIUM_HIGH, + FAN_MEDIUM_LOW, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, @@ -31,6 +38,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.translation import async_get_translations from . import setup_integration from .conftest import DEVICE_ADDRESS, ENTITY_ID, FakeGateway, build_status @@ -385,3 +393,62 @@ async def test_device_address_is_used_for_the_entity( assert hass.states.get(ENTITY_ID) is not None assert hass.states.get("climate.ac_1_2") is not None + + +async def test_every_fan_mode_has_a_name( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_gateway: FakeGateway, + mock_config_entry: MockConfigEntry, +) -> None: + """Test every speed the entity offers is named in the interface. + + The climate component names three of the five speeds the gateway can + address. The other two have to be named here, or they reach the user as + the keys themselves. + """ + await setup_integration(hass, mock_config_entry) + + state = hass.states.get(ENTITY_ID) + assert state + entry = entity_registry.async_get(ENTITY_ID) + assert entry + + # Asked for one integration at a time, these come back as the cache itself + # rather than a copy of it, so neither is merged into the other. + named_by_component = await async_get_translations( + hass, "en", "entity_component", [CLIMATE_DOMAIN] + ) + named_here = await async_get_translations(hass, "en", "entity", [DOMAIN]) + + by_component = ( + f"component.{CLIMATE_DOMAIN}.entity_component._" + ".state_attributes.fan_mode.state." + ) + by_integration = ( + f"component.{DOMAIN}.entity.climate.{entry.translation_key}" + ".state_attributes.fan_mode.state." + ) + fan_modes = state.attributes[ATTR_FAN_MODES] + + assert fan_modes == ALL_FAN_MODES + + # The two the component has no name for are named here, and only those + # two: the others are left to it, which is what lets them keep the names + # it gives them. + assert {mode: named_here.get(f"{by_integration}{mode}") for mode in fan_modes} == { + FAN_LOW: None, + FAN_MEDIUM_LOW: "Medium low", + FAN_MIDDLE: None, + FAN_MEDIUM_HIGH: "Medium high", + FAN_HIGH: None, + } + assert { + mode: named_by_component.get(f"{by_component}{mode}") for mode in fan_modes + } == { + FAN_LOW: "Low", + FAN_MEDIUM_LOW: None, + FAN_MIDDLE: "Middle", + FAN_MEDIUM_HIGH: None, + FAN_HIGH: "High", + }