From d2030858db5b5a010d7e6d08e0e2e65fedd8f3e7 Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Wed, 18 Feb 2026 06:38:51 -0800 Subject: [PATCH 1/8] Add Roborock dock control buttons and mop wash mode select Expose three dock action buttons (empty dustbin, wash mop, stop drying) and a mop wash mode select entity, using the existing wash_towel_mode and dust_collection_mode traits. Entities are only created when the device hardware supports the relevant trait. Co-Authored-By: Claude Sonnet 4.6 --- homeassistant/components/roborock/button.py | 64 +++++++++++++++ homeassistant/components/roborock/select.py | 19 +++++ .../components/roborock/strings.json | 18 ++++ tests/components/roborock/conftest.py | 2 + tests/components/roborock/test_button.py | 82 ++++++++++++++++++- 5 files changed, 184 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roborock/button.py b/homeassistant/components/roborock/button.py index 65f2e1713596c..932593334572a 100644 --- a/homeassistant/components/roborock/button.py +++ b/homeassistant/components/roborock/button.py @@ -3,14 +3,17 @@ from __future__ import annotations import asyncio +from collections.abc import Callable from dataclasses import dataclass import itertools import logging from typing import Any +from roborock.devices.traits.v1 import PropertiesApi from roborock.devices.traits.v1.consumeable import ConsumableAttribute from roborock.exceptions import RoborockException from roborock.roborock_message import RoborockZeoProtocol +from roborock.roborock_typing import RoborockCommand from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.const import EntityCategory @@ -97,6 +100,37 @@ class RoborockButtonDescriptionA01(ButtonEntityDescription): ] +@dataclass(frozen=True, kw_only=True) +class RoborockDockCommandButtonDescription(ButtonEntityDescription): + """Describes a Roborock dock command button entity.""" + + api_command: RoborockCommand + availability_fn: Callable[[PropertiesApi], bool] + """Return True if this button should be created for the given device.""" + + +DOCK_COMMAND_BUTTON_DESCRIPTIONS: list[RoborockDockCommandButtonDescription] = [ + RoborockDockCommandButtonDescription( + key="empty", + translation_key="dock_empty", + api_command=RoborockCommand.APP_START_COLLECT_DUST, + availability_fn=lambda api: api.dust_collection_mode is not None, + ), + RoborockDockCommandButtonDescription( + key="wash_mop", + translation_key="dock_wash_mop", + api_command=RoborockCommand.APP_START_WASH, + availability_fn=lambda api: api.wash_towel_mode is not None, + ), + RoborockDockCommandButtonDescription( + key="stop_drying", + translation_key="dock_stop_drying", + api_command=RoborockCommand.APP_STOP_WASH, + availability_fn=lambda api: api.wash_towel_mode is not None, + ), +] + + async def async_setup_entry( hass: HomeAssistant, config_entry: RoborockConfigEntry, @@ -139,6 +173,13 @@ async def async_setup_entry( if isinstance(coordinator, RoborockWashingMachineUpdateCoordinator) for description in ZEO_BUTTON_DESCRIPTIONS ), + ( + RoborockDockCommandButtonEntity(coordinator, description) + for coordinator in config_entry.runtime_data.v1 + for description in DOCK_COMMAND_BUTTON_DESCRIPTIONS + if isinstance(coordinator, RoborockDataUpdateCoordinator) + if description.availability_fn(coordinator.properties_api) + ), ) ) @@ -233,3 +274,26 @@ async def async_press(self) -> None: ) from err finally: await self.coordinator.async_request_refresh() + + +class RoborockDockCommandButtonEntity(RoborockEntityV1, ButtonEntity): + """A class to define Roborock dock command button entities.""" + + entity_description: RoborockDockCommandButtonDescription + + def __init__( + self, + coordinator: RoborockDataUpdateCoordinator, + entity_description: RoborockDockCommandButtonDescription, + ) -> None: + """Create a dock command button entity.""" + super().__init__( + f"{entity_description.key}_{coordinator.duid_slug}", + coordinator.dock_device_info, + api=coordinator.properties_api.command, + ) + self.entity_description = entity_description + + async def async_press(self) -> None: + """Send the dock command.""" + await self.send(self.entity_description.api_command) diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index 0ff27d8145f94..e300d1b2c23bf 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -9,6 +9,7 @@ from roborock import B01Props, CleanTypeMapping from roborock.data import ( RoborockDockDustCollectionModeCode, + RoborockDockWashTowelModeCode, RoborockEnum, WaterLevelMapping, ZeoDetergentType, @@ -167,6 +168,24 @@ class RoborockSelectDescriptionA01(SelectEntityDescription): ], is_dock_entity=True, ), + RoborockSelectDescription( + key="wash_towel_mode", + translation_key="mop_wash_mode", + api_command=RoborockCommand.SET_WASH_TOWEL_MODE, + value_fn=lambda api: ( + mode.name if (mode := api.wash_towel_mode.wash_mode) is not None else None # type: ignore[union-attr] + ), + entity_category=EntityCategory.CONFIG, + options_lambda=lambda api: ( + RoborockDockWashTowelModeCode.keys() + if api.wash_towel_mode is not None + else None + ), + parameter_lambda=lambda key, _: [ + RoborockDockWashTowelModeCode.as_dict().get(key) + ], + is_dock_entity=True, + ), ] diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index 39eeed3e0f8a5..3142a3595443d 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -84,6 +84,15 @@ } }, "button": { + "dock_empty": { + "name": "Empty" + }, + "dock_stop_drying": { + "name": "Stop drying" + }, + "dock_wash_mop": { + "name": "Wash mop" + }, "pause": { "name": "Pause" }, @@ -193,6 +202,15 @@ "standard": "Standard" } }, + "mop_wash_mode": { + "name": "Mop wash mode", + "state": { + "light": "[%key:component::roborock::entity::select::dust_collection_mode::state::light%]", + "balanced": "[%key:component::roborock::entity::vacuum::roborock::state_attributes::fan_speed::state::balanced%]", + "deep": "[%key:component::roborock::entity::select::mop_mode::state::deep%]", + "smart": "[%key:component::roborock::entity::select::dust_collection_mode::state::smart%]" + } + }, "program": { "name": "Wash program", "state": { diff --git a/tests/components/roborock/conftest.py b/tests/components/roborock/conftest.py index 08c75f234e96c..e653d5102d151 100644 --- a/tests/components/roborock/conftest.py +++ b/tests/components/roborock/conftest.py @@ -27,6 +27,7 @@ NamedRoomMapping, NetworkInfo, RoborockBase, + RoborockDockWashTowelModeCode, RoborockDyadStateCode, ValleyElectricityTimer, WorkStatusMapping, @@ -374,6 +375,7 @@ def create_v1_properties(network_info: NetworkInfo) -> AsyncMock: trait_spec=DustCollectionModeTrait ) v1_properties.wash_towel_mode = make_mock_trait(trait_spec=WashTowelModeTrait) + v1_properties.wash_towel_mode.wash_mode = RoborockDockWashTowelModeCode.light v1_properties.smart_wash_params = make_mock_trait(trait_spec=SmartWashParamsTrait) v1_properties.home = make_home_trait( map_info=MULTI_MAP_LIST.map_info, diff --git a/tests/components/roborock/test_button.py b/tests/components/roborock/test_button.py index 287bbf4c82f99..bc5f780cfd439 100644 --- a/tests/components/roborock/test_button.py +++ b/tests/components/roborock/test_button.py @@ -1,17 +1,20 @@ """Test Roborock Button platform.""" -from unittest.mock import Mock +from unittest.mock import Mock, call import pytest from roborock import RoborockException from roborock.exceptions import RoborockTimeout +from roborock.roborock_typing import RoborockCommand from syrupy.assertion import SnapshotAssertion from homeassistant.components.button import SERVICE_PRESS +from homeassistant.components.roborock import DOMAIN from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component from .conftest import FakeDevice @@ -272,3 +275,80 @@ async def test_press_a01_button_failure( washing_machine.zeo.set_value.assert_called_once() assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00" + + +@pytest.mark.parametrize( + ("entity_id", "expected_command"), + [ + ( + "button.roborock_s7_maxv_dock_empty", + RoborockCommand.APP_START_COLLECT_DUST, + ), + ( + "button.roborock_s7_maxv_dock_wash_mop", + RoborockCommand.APP_START_WASH, + ), + ( + "button.roborock_s7_maxv_dock_stop_drying", + RoborockCommand.APP_STOP_WASH, + ), + ], +) +@pytest.mark.freeze_time("2023-10-30 08:50:00") +async def test_press_dock_command_button_success( + hass: HomeAssistant, + setup_entry: MockConfigEntry, + entity_id: str, + expected_command: RoborockCommand, + fake_vacuum: FakeDevice, +) -> None: + """Test pressing dock command button entities sends the correct command.""" + assert hass.states.get(entity_id) is not None + await hass.services.async_call( + "button", + SERVICE_PRESS, + blocking=True, + target={"entity_id": entity_id}, + ) + assert fake_vacuum.v1_properties + assert fake_vacuum.v1_properties.command.send.call_args == call( + expected_command, params=None + ) + assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00" + + +async def test_press_dock_command_button_failure( + hass: HomeAssistant, + setup_entry: MockConfigEntry, + fake_vacuum: FakeDevice, +) -> None: + """Test that a dock command button raises HomeAssistantError on failure.""" + assert fake_vacuum.v1_properties + fake_vacuum.v1_properties.command.send.side_effect = RoborockException + + with pytest.raises( + HomeAssistantError, match="Error while calling APP_START_COLLECT_DUST" + ): + await hass.services.async_call( + "button", + SERVICE_PRESS, + blocking=True, + target={"entity_id": "button.roborock_s7_maxv_dock_empty"}, + ) + + +async def test_dock_command_buttons_not_created_without_traits( + hass: HomeAssistant, + mock_roborock_entry: MockConfigEntry, + fake_vacuum: FakeDevice, +) -> None: + """Test that dock command buttons are not created when traits are unavailable.""" + assert fake_vacuum.v1_properties + fake_vacuum.v1_properties.dust_collection_mode = None + fake_vacuum.v1_properties.wash_towel_mode = None + + await async_setup_component(hass, DOMAIN, {}) + + assert hass.states.get("button.roborock_s7_maxv_dock_empty") is None + assert hass.states.get("button.roborock_s7_maxv_dock_wash_mop") is None + assert hass.states.get("button.roborock_s7_maxv_dock_stop_drying") is None From 79f7055eaa820b6d6db7e892acb085d3ca7d5d14 Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Wed, 18 Feb 2026 10:39:09 -0800 Subject: [PATCH 2/8] Address Copilot review comments on dock control PR - Fix dock button entity keys to include dock_ prefix (dock_empty, dock_wash_mop, dock_stop_drying) so unique_ids are stable and match the intent of the translation keys - Add **kwargs: Any to RoborockDockCommandButtonEntity.async_press for consistency with ButtonEntity contract - Guard wash_towel_mode None check in value_fn to prevent AttributeError at runtime; use non-optional dict lookup in parameter_lambda to avoid sending [None] as a command parameter - Replace cross-entity translation references in mop_wash_mode.state with direct literal strings to avoid coupling to unrelated entities --- homeassistant/components/roborock/button.py | 8 ++++---- homeassistant/components/roborock/select.py | 7 +++++-- homeassistant/components/roborock/strings.json | 8 ++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/roborock/button.py b/homeassistant/components/roborock/button.py index 932593334572a..6dbc74ae11de7 100644 --- a/homeassistant/components/roborock/button.py +++ b/homeassistant/components/roborock/button.py @@ -111,19 +111,19 @@ class RoborockDockCommandButtonDescription(ButtonEntityDescription): DOCK_COMMAND_BUTTON_DESCRIPTIONS: list[RoborockDockCommandButtonDescription] = [ RoborockDockCommandButtonDescription( - key="empty", + key="dock_empty", translation_key="dock_empty", api_command=RoborockCommand.APP_START_COLLECT_DUST, availability_fn=lambda api: api.dust_collection_mode is not None, ), RoborockDockCommandButtonDescription( - key="wash_mop", + key="dock_wash_mop", translation_key="dock_wash_mop", api_command=RoborockCommand.APP_START_WASH, availability_fn=lambda api: api.wash_towel_mode is not None, ), RoborockDockCommandButtonDescription( - key="stop_drying", + key="dock_stop_drying", translation_key="dock_stop_drying", api_command=RoborockCommand.APP_STOP_WASH, availability_fn=lambda api: api.wash_towel_mode is not None, @@ -294,6 +294,6 @@ def __init__( ) self.entity_description = entity_description - async def async_press(self) -> None: + async def async_press(self, **kwargs: Any) -> None: """Send the dock command.""" await self.send(self.entity_description.api_command) diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index e300d1b2c23bf..3c788a4a54353 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -173,7 +173,10 @@ class RoborockSelectDescriptionA01(SelectEntityDescription): translation_key="mop_wash_mode", api_command=RoborockCommand.SET_WASH_TOWEL_MODE, value_fn=lambda api: ( - mode.name if (mode := api.wash_towel_mode.wash_mode) is not None else None # type: ignore[union-attr] + mode.name + if api.wash_towel_mode is not None + and (mode := api.wash_towel_mode.wash_mode) is not None + else None ), entity_category=EntityCategory.CONFIG, options_lambda=lambda api: ( @@ -182,7 +185,7 @@ class RoborockSelectDescriptionA01(SelectEntityDescription): else None ), parameter_lambda=lambda key, _: [ - RoborockDockWashTowelModeCode.as_dict().get(key) + RoborockDockWashTowelModeCode.as_dict()[key] ], is_dock_entity=True, ), diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index 3142a3595443d..1aa8b189ae030 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -205,10 +205,10 @@ "mop_wash_mode": { "name": "Mop wash mode", "state": { - "light": "[%key:component::roborock::entity::select::dust_collection_mode::state::light%]", - "balanced": "[%key:component::roborock::entity::vacuum::roborock::state_attributes::fan_speed::state::balanced%]", - "deep": "[%key:component::roborock::entity::select::mop_mode::state::deep%]", - "smart": "[%key:component::roborock::entity::select::dust_collection_mode::state::smart%]" + "light": "Light", + "balanced": "Balanced", + "deep": "Deep", + "smart": "Smart" } }, "program": { From 7665636dce5cf81825a158241161bebe6a07d1eb Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Wed, 18 Feb 2026 15:49:35 -0800 Subject: [PATCH 3/8] Fix pre-commit failures and address review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ruff format: collapse single-element lambda list onto one line in select.py - prettier: sort strings.json state keys alphabetically (light → after deep) - Add entity_registry_enabled_default=False to all dock command buttons - Add comment explaining wash_towel_mode as mop-wash-station capability proxy - Add entity_registry_enabled_by_default to dock button press tests - Add mop_wash_mode select tests: current state, option change, absent when None Co-Authored-By: Claude Sonnet 4.6 --- homeassistant/components/roborock/button.py | 4 ++ homeassistant/components/roborock/select.py | 4 +- .../components/roborock/strings.json | 2 +- tests/components/roborock/test_button.py | 2 + tests/components/roborock/test_select.py | 57 +++++++++++++++++++ 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/roborock/button.py b/homeassistant/components/roborock/button.py index 6dbc74ae11de7..c6b3cf541c6e1 100644 --- a/homeassistant/components/roborock/button.py +++ b/homeassistant/components/roborock/button.py @@ -115,18 +115,22 @@ class RoborockDockCommandButtonDescription(ButtonEntityDescription): translation_key="dock_empty", api_command=RoborockCommand.APP_START_COLLECT_DUST, availability_fn=lambda api: api.dust_collection_mode is not None, + entity_registry_enabled_default=False, ), RoborockDockCommandButtonDescription( key="dock_wash_mop", translation_key="dock_wash_mop", api_command=RoborockCommand.APP_START_WASH, + # wash_towel_mode being non-None is the API proxy for "has mop washing station" availability_fn=lambda api: api.wash_towel_mode is not None, + entity_registry_enabled_default=False, ), RoborockDockCommandButtonDescription( key="dock_stop_drying", translation_key="dock_stop_drying", api_command=RoborockCommand.APP_STOP_WASH, availability_fn=lambda api: api.wash_towel_mode is not None, + entity_registry_enabled_default=False, ), ] diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index 3c788a4a54353..5271a112ef59d 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -184,9 +184,7 @@ class RoborockSelectDescriptionA01(SelectEntityDescription): if api.wash_towel_mode is not None else None ), - parameter_lambda=lambda key, _: [ - RoborockDockWashTowelModeCode.as_dict()[key] - ], + parameter_lambda=lambda key, _: [RoborockDockWashTowelModeCode.as_dict()[key]], is_dock_entity=True, ), ] diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index 1aa8b189ae030..4c120c19344ba 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -205,9 +205,9 @@ "mop_wash_mode": { "name": "Mop wash mode", "state": { - "light": "Light", "balanced": "Balanced", "deep": "Deep", + "light": "Light", "smart": "Smart" } }, diff --git a/tests/components/roborock/test_button.py b/tests/components/roborock/test_button.py index bc5f780cfd439..7b3189a0376fe 100644 --- a/tests/components/roborock/test_button.py +++ b/tests/components/roborock/test_button.py @@ -295,6 +295,7 @@ async def test_press_a01_button_failure( ], ) @pytest.mark.freeze_time("2023-10-30 08:50:00") +@pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_press_dock_command_button_success( hass: HomeAssistant, setup_entry: MockConfigEntry, @@ -317,6 +318,7 @@ async def test_press_dock_command_button_success( assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00" +@pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_press_dock_command_button_failure( hass: HomeAssistant, setup_entry: MockConfigEntry, diff --git a/tests/components/roborock/test_select.py b/tests/components/roborock/test_select.py index 31e77ee29c8af..09f04b48b1647 100644 --- a/tests/components/roborock/test_select.py +++ b/tests/components/roborock/test_select.py @@ -7,6 +7,7 @@ from roborock import CleanTypeMapping, RoborockCommand from roborock.data import ( RoborockDockDustCollectionModeCode, + RoborockDockWashTowelModeCode, WaterLevelMapping, ZeoProgram, ) @@ -383,3 +384,59 @@ async def test_update_failure_zeo_invalid_option() -> None: await entity.async_select_option("invalid_option") coordinator.api.set_value.assert_not_called() + + +async def test_mop_wash_mode_state( + hass: HomeAssistant, + mock_roborock_entry: MockConfigEntry, + fake_vacuum: FakeDevice, +) -> None: + """Test that the mop wash mode select shows the correct current mode.""" + assert fake_vacuum.v1_properties + assert fake_vacuum.v1_properties.wash_towel_mode + fake_vacuum.v1_properties.wash_towel_mode.wash_mode = RoborockDockWashTowelModeCode.deep + + await async_setup_component(hass, DOMAIN, {}) + + select_entity = hass.states.get("select.roborock_s7_maxv_dock_mop_wash_mode") + assert select_entity + assert select_entity.state == "deep" + + +async def test_mop_wash_mode_select( + hass: HomeAssistant, + setup_entry: MockConfigEntry, + fake_vacuum: FakeDevice, +) -> None: + """Test that selecting a mop wash mode sends the correct command.""" + entity_id = "select.roborock_s7_maxv_dock_mop_wash_mode" + assert hass.states.get(entity_id) is not None + + await hass.services.async_call( + "select", + SERVICE_SELECT_OPTION, + service_data={"option": "deep"}, + blocking=True, + target={"entity_id": entity_id}, + ) + + assert fake_vacuum.v1_properties + assert fake_vacuum.v1_properties.command.send.call_count == 1 + assert fake_vacuum.v1_properties.command.send.call_args == call( + RoborockCommand.SET_WASH_TOWEL_MODE, + params=[RoborockDockWashTowelModeCode.as_dict()["deep"]], + ) + + +async def test_mop_wash_mode_not_created_when_none( + hass: HomeAssistant, + mock_roborock_entry: MockConfigEntry, + fake_vacuum: FakeDevice, +) -> None: + """Test that the mop wash mode select is absent when wash_towel_mode is None.""" + assert fake_vacuum.v1_properties + fake_vacuum.v1_properties.wash_towel_mode = None + + await async_setup_component(hass, DOMAIN, {}) + + assert hass.states.get("select.roborock_s7_maxv_dock_mop_wash_mode") is None From df357f484353b6bc5ecd15b53d706b0b0d72c0ba Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Wed, 18 Feb 2026 16:07:13 -0800 Subject: [PATCH 4/8] Address Copilot review comments on dock control PR - select: use .get(key) in parameter_lambda for consistency with dust_collection_mode - test_button: add bypass_api_client_fixture to dock command button tests - test_select: add mop_wash_mode tests (current state, option change, absent when None) Co-Authored-By: Claude Sonnet 4.6 --- homeassistant/components/roborock/select.py | 4 +++- tests/components/roborock/test_button.py | 2 ++ tests/components/roborock/test_select.py | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index 5271a112ef59d..b8e3867e3b3e0 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -184,7 +184,9 @@ class RoborockSelectDescriptionA01(SelectEntityDescription): if api.wash_towel_mode is not None else None ), - parameter_lambda=lambda key, _: [RoborockDockWashTowelModeCode.as_dict()[key]], + parameter_lambda=lambda key, _: [ + RoborockDockWashTowelModeCode.as_dict().get(key) + ], is_dock_entity=True, ), ] diff --git a/tests/components/roborock/test_button.py b/tests/components/roborock/test_button.py index 7b3189a0376fe..8699f896200f6 100644 --- a/tests/components/roborock/test_button.py +++ b/tests/components/roborock/test_button.py @@ -298,6 +298,7 @@ async def test_press_a01_button_failure( @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_press_dock_command_button_success( hass: HomeAssistant, + bypass_api_client_fixture: None, setup_entry: MockConfigEntry, entity_id: str, expected_command: RoborockCommand, @@ -321,6 +322,7 @@ async def test_press_dock_command_button_success( @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_press_dock_command_button_failure( hass: HomeAssistant, + bypass_api_client_fixture: None, setup_entry: MockConfigEntry, fake_vacuum: FakeDevice, ) -> None: diff --git a/tests/components/roborock/test_select.py b/tests/components/roborock/test_select.py index 09f04b48b1647..6b07116d04ed4 100644 --- a/tests/components/roborock/test_select.py +++ b/tests/components/roborock/test_select.py @@ -394,7 +394,9 @@ async def test_mop_wash_mode_state( """Test that the mop wash mode select shows the correct current mode.""" assert fake_vacuum.v1_properties assert fake_vacuum.v1_properties.wash_towel_mode - fake_vacuum.v1_properties.wash_towel_mode.wash_mode = RoborockDockWashTowelModeCode.deep + fake_vacuum.v1_properties.wash_towel_mode.wash_mode = ( + RoborockDockWashTowelModeCode.deep + ) await async_setup_component(hass, DOMAIN, {}) From 7e0e441f9174548712588654872477a504022ee5 Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Wed, 18 Feb 2026 17:46:42 -0800 Subject: [PATCH 5/8] Fix trait-gating test to use entity registry instead of states Disabled-by-default entities have no state entry, so hass.states.get() would return None even if the entities were actually created. Use the entity registry to reliably verify entities are absent when traits are unavailable. Co-Authored-By: Claude Sonnet 4.6 --- tests/components/roborock/test_button.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/components/roborock/test_button.py b/tests/components/roborock/test_button.py index 8699f896200f6..798e8c1441a5c 100644 --- a/tests/components/roborock/test_button.py +++ b/tests/components/roborock/test_button.py @@ -345,6 +345,7 @@ async def test_dock_command_buttons_not_created_without_traits( hass: HomeAssistant, mock_roborock_entry: MockConfigEntry, fake_vacuum: FakeDevice, + entity_registry: er.EntityRegistry, ) -> None: """Test that dock command buttons are not created when traits are unavailable.""" assert fake_vacuum.v1_properties @@ -353,6 +354,6 @@ async def test_dock_command_buttons_not_created_without_traits( await async_setup_component(hass, DOMAIN, {}) - assert hass.states.get("button.roborock_s7_maxv_dock_empty") is None - assert hass.states.get("button.roborock_s7_maxv_dock_wash_mop") is None - assert hass.states.get("button.roborock_s7_maxv_dock_stop_drying") is None + assert entity_registry.async_get("button.roborock_s7_maxv_dock_empty") is None + assert entity_registry.async_get("button.roborock_s7_maxv_dock_wash_mop") is None + assert entity_registry.async_get("button.roborock_s7_maxv_dock_stop_drying") is None From 69b570bd43f2e605cb2d05afd5e0a79df42634e8 Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Sat, 28 Feb 2026 09:24:00 -0800 Subject: [PATCH 6/8] Update test snapshots after rebase onto dev Co-Authored-By: Claude Opus 4.6 --- .../roborock/snapshots/test_button.ambr | 441 ++++++++++++++++++ 1 file changed, 441 insertions(+) diff --git a/tests/components/roborock/snapshots/test_button.ambr b/tests/components/roborock/snapshots/test_button.ambr index dc4ea7ca120cd..8bdf7d8fa345f 100644 --- a/tests/components/roborock/snapshots/test_button.ambr +++ b/tests/components/roborock/snapshots/test_button.ambr @@ -1,4 +1,151 @@ # serializer version: 1 +# name: test_buttons[button.roborock_s7_2_dock_empty-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.roborock_s7_2_dock_empty', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Empty', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Empty', + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dock_empty', + 'unique_id': 'dock_empty_device_2', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.roborock_s7_2_dock_empty-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Roborock S7 2 Dock Empty', + }), + 'context': , + 'entity_id': 'button.roborock_s7_2_dock_empty', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_buttons[button.roborock_s7_2_dock_stop_drying-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.roborock_s7_2_dock_stop_drying', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Stop drying', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Stop drying', + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dock_stop_drying', + 'unique_id': 'dock_stop_drying_device_2', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.roborock_s7_2_dock_stop_drying-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Roborock S7 2 Dock Stop drying', + }), + 'context': , + 'entity_id': 'button.roborock_s7_2_dock_stop_drying', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_buttons[button.roborock_s7_2_dock_wash_mop-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.roborock_s7_2_dock_wash_mop', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Wash mop', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Wash mop', + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dock_wash_mop', + 'unique_id': 'dock_wash_mop_device_2', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.roborock_s7_2_dock_wash_mop-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Roborock S7 2 Dock Wash mop', + }), + 'context': , + 'entity_id': 'button.roborock_s7_2_dock_wash_mop', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_buttons[button.roborock_s7_2_reset_air_filter_consumable-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -293,6 +440,153 @@ 'state': 'unknown', }) # --- +# name: test_buttons[button.roborock_s7_maxv_dock_empty-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.roborock_s7_maxv_dock_empty', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Empty', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Empty', + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dock_empty', + 'unique_id': 'dock_empty_abc123', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.roborock_s7_maxv_dock_empty-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Roborock S7 MaxV Dock Empty', + }), + 'context': , + 'entity_id': 'button.roborock_s7_maxv_dock_empty', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_buttons[button.roborock_s7_maxv_dock_stop_drying-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.roborock_s7_maxv_dock_stop_drying', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Stop drying', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Stop drying', + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dock_stop_drying', + 'unique_id': 'dock_stop_drying_abc123', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.roborock_s7_maxv_dock_stop_drying-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Roborock S7 MaxV Dock Stop drying', + }), + 'context': , + 'entity_id': 'button.roborock_s7_maxv_dock_stop_drying', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_buttons[button.roborock_s7_maxv_dock_wash_mop-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.roborock_s7_maxv_dock_wash_mop', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Wash mop', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Wash mop', + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dock_wash_mop', + 'unique_id': 'dock_wash_mop_abc123', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.roborock_s7_maxv_dock_wash_mop-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Roborock S7 MaxV Dock Wash mop', + }), + 'context': , + 'entity_id': 'button.roborock_s7_maxv_dock_wash_mop', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_buttons[button.roborock_s7_maxv_reset_air_filter_consumable-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -587,6 +881,153 @@ 'state': 'unknown', }) # --- +# name: test_buttons[button.zeo_one-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.zeo_one', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'start', + 'unique_id': 'start_zeo_duid', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.zeo_one-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Zeo One', + }), + 'context': , + 'entity_id': 'button.zeo_one', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_buttons[button.zeo_one_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.zeo_one_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'pause', + 'unique_id': 'pause_zeo_duid', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.zeo_one_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Zeo One', + }), + 'context': , + 'entity_id': 'button.zeo_one_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_buttons[button.zeo_one_3-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': None, + 'entity_id': 'button.zeo_one_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'roborock', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'shutdown', + 'unique_id': 'shutdown_zeo_duid', + 'unit_of_measurement': None, + }) +# --- +# name: test_buttons[button.zeo_one_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Zeo One', + }), + 'context': , + 'entity_id': 'button.zeo_one_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_buttons[button.zeo_one_pause-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From 3eb59535160abb21a4d32e5ca34f4d7553e2d9c9 Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Fri, 6 Mar 2026 08:39:25 -0800 Subject: [PATCH 7/8] Remove stale Zeo button snapshots Delete 6 unused snapshot entries (button.zeo_one, _2, _3) that were generated during an earlier snapshot update when entity naming was broken. The correctly-named snapshots (_pause, _shutdown, _start) remain. Co-Authored-By: Claude Opus 4.6 --- .../roborock/snapshots/test_button.ambr | 147 ------------------ 1 file changed, 147 deletions(-) diff --git a/tests/components/roborock/snapshots/test_button.ambr b/tests/components/roborock/snapshots/test_button.ambr index 8bdf7d8fa345f..0ca6ca385f066 100644 --- a/tests/components/roborock/snapshots/test_button.ambr +++ b/tests/components/roborock/snapshots/test_button.ambr @@ -881,153 +881,6 @@ 'state': 'unknown', }) # --- -# name: test_buttons[button.zeo_one-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'button', - 'entity_category': None, - 'entity_id': 'button.zeo_one', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'roborock', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'start', - 'unique_id': 'start_zeo_duid', - 'unit_of_measurement': None, - }) -# --- -# name: test_buttons[button.zeo_one-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Zeo One', - }), - 'context': , - 'entity_id': 'button.zeo_one', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'unknown', - }) -# --- -# name: test_buttons[button.zeo_one_2-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'button', - 'entity_category': None, - 'entity_id': 'button.zeo_one_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'roborock', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'pause', - 'unique_id': 'pause_zeo_duid', - 'unit_of_measurement': None, - }) -# --- -# name: test_buttons[button.zeo_one_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Zeo One', - }), - 'context': , - 'entity_id': 'button.zeo_one_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'unknown', - }) -# --- -# name: test_buttons[button.zeo_one_3-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'button', - 'entity_category': None, - 'entity_id': 'button.zeo_one_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'roborock', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'shutdown', - 'unique_id': 'shutdown_zeo_duid', - 'unit_of_measurement': None, - }) -# --- -# name: test_buttons[button.zeo_one_3-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Zeo One', - }), - 'context': , - 'entity_id': 'button.zeo_one_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'unknown', - }) -# --- # name: test_buttons[button.zeo_one_pause-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From 7fe1966244df8a99f8268601d2af9ee2f1d8e654 Mon Sep 17 00:00:00 2001 From: Iain Swarts Date: Sun, 8 Mar 2026 22:43:32 -0700 Subject: [PATCH 8/8] Remove mop wash mode select entity per maintainer request Lash-L noted the wash towel modes are dynamic per device, not a fixed enum. Removing select entity until python-roborock#765 lands with proper per-device mode support. Dock buttons retained. Co-Authored-By: Claude Opus 4.6 --- homeassistant/components/roborock/select.py | 22 ------- .../components/roborock/strings.json | 9 --- tests/components/roborock/test_select.py | 59 ------------------- 3 files changed, 90 deletions(-) diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index b8e3867e3b3e0..0ff27d8145f94 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -9,7 +9,6 @@ from roborock import B01Props, CleanTypeMapping from roborock.data import ( RoborockDockDustCollectionModeCode, - RoborockDockWashTowelModeCode, RoborockEnum, WaterLevelMapping, ZeoDetergentType, @@ -168,27 +167,6 @@ class RoborockSelectDescriptionA01(SelectEntityDescription): ], is_dock_entity=True, ), - RoborockSelectDescription( - key="wash_towel_mode", - translation_key="mop_wash_mode", - api_command=RoborockCommand.SET_WASH_TOWEL_MODE, - value_fn=lambda api: ( - mode.name - if api.wash_towel_mode is not None - and (mode := api.wash_towel_mode.wash_mode) is not None - else None - ), - entity_category=EntityCategory.CONFIG, - options_lambda=lambda api: ( - RoborockDockWashTowelModeCode.keys() - if api.wash_towel_mode is not None - else None - ), - parameter_lambda=lambda key, _: [ - RoborockDockWashTowelModeCode.as_dict().get(key) - ], - is_dock_entity=True, - ), ] diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index 4c120c19344ba..11504914cbde5 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -202,15 +202,6 @@ "standard": "Standard" } }, - "mop_wash_mode": { - "name": "Mop wash mode", - "state": { - "balanced": "Balanced", - "deep": "Deep", - "light": "Light", - "smart": "Smart" - } - }, "program": { "name": "Wash program", "state": { diff --git a/tests/components/roborock/test_select.py b/tests/components/roborock/test_select.py index 6b07116d04ed4..31e77ee29c8af 100644 --- a/tests/components/roborock/test_select.py +++ b/tests/components/roborock/test_select.py @@ -7,7 +7,6 @@ from roborock import CleanTypeMapping, RoborockCommand from roborock.data import ( RoborockDockDustCollectionModeCode, - RoborockDockWashTowelModeCode, WaterLevelMapping, ZeoProgram, ) @@ -384,61 +383,3 @@ async def test_update_failure_zeo_invalid_option() -> None: await entity.async_select_option("invalid_option") coordinator.api.set_value.assert_not_called() - - -async def test_mop_wash_mode_state( - hass: HomeAssistant, - mock_roborock_entry: MockConfigEntry, - fake_vacuum: FakeDevice, -) -> None: - """Test that the mop wash mode select shows the correct current mode.""" - assert fake_vacuum.v1_properties - assert fake_vacuum.v1_properties.wash_towel_mode - fake_vacuum.v1_properties.wash_towel_mode.wash_mode = ( - RoborockDockWashTowelModeCode.deep - ) - - await async_setup_component(hass, DOMAIN, {}) - - select_entity = hass.states.get("select.roborock_s7_maxv_dock_mop_wash_mode") - assert select_entity - assert select_entity.state == "deep" - - -async def test_mop_wash_mode_select( - hass: HomeAssistant, - setup_entry: MockConfigEntry, - fake_vacuum: FakeDevice, -) -> None: - """Test that selecting a mop wash mode sends the correct command.""" - entity_id = "select.roborock_s7_maxv_dock_mop_wash_mode" - assert hass.states.get(entity_id) is not None - - await hass.services.async_call( - "select", - SERVICE_SELECT_OPTION, - service_data={"option": "deep"}, - blocking=True, - target={"entity_id": entity_id}, - ) - - assert fake_vacuum.v1_properties - assert fake_vacuum.v1_properties.command.send.call_count == 1 - assert fake_vacuum.v1_properties.command.send.call_args == call( - RoborockCommand.SET_WASH_TOWEL_MODE, - params=[RoborockDockWashTowelModeCode.as_dict()["deep"]], - ) - - -async def test_mop_wash_mode_not_created_when_none( - hass: HomeAssistant, - mock_roborock_entry: MockConfigEntry, - fake_vacuum: FakeDevice, -) -> None: - """Test that the mop wash mode select is absent when wash_towel_mode is None.""" - assert fake_vacuum.v1_properties - fake_vacuum.v1_properties.wash_towel_mode = None - - await async_setup_component(hass, DOMAIN, {}) - - assert hass.states.get("select.roborock_s7_maxv_dock_mop_wash_mode") is None