diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index a608c837d5ca..7731491a4c78 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -352,6 +352,12 @@ class BraavaJet(IRobotVacuum): for spray in BRAAVA_SPRAY_AMOUNT ] + # Combo models report a mop pad but not `rankOverlap`, which the mop + # behavior is derived from. + if self.vacuum_state.get("rankOverlap") is None: + self._attr_supported_features &= ~VacuumEntityFeature.FAN_SPEED + self._attr_fan_speed_list = [] + @property @override def fan_speed(self) -> str: diff --git a/tests/components/roomba/test_vacuum.py b/tests/components/roomba/test_vacuum.py index 1533ab445927..e9ba408da431 100644 --- a/tests/components/roomba/test_vacuum.py +++ b/tests/components/roomba/test_vacuum.py @@ -1,5 +1,6 @@ """Tests for the Roomba vacuum platform.""" +from typing import Any from unittest.mock import AsyncMock, patch import pytest @@ -9,6 +10,7 @@ from homeassistant.components.vacuum import ( DOMAIN as VACUUM_DOMAIN, SERVICE_SET_FAN_SPEED, VacuumActivity, + VacuumEntityFeature, ) from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant @@ -68,6 +70,43 @@ async def test_vacuum_activity( assert state.state == expected +@pytest.mark.parametrize( + ("extra_state", "expect_fan_speed_support", "expected_fan_speed"), + [ + # 67 is OVERLAP_STANDARD. + ({"rankOverlap": 67}, True, "Standard-1"), + # Combo models report a mop pad but no rankOverlap. + ({}, False, None), + ], +) +async def test_braava_fan_speed_requires_rank_overlap( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_roomba: AsyncMock, + extra_state: dict[str, Any], + expect_fan_speed_support: bool, + expected_fan_speed: str | None, +) -> None: + """Test that fan speed is only offered when it can be produced.""" + reported = mock_roomba.master_state["state"]["reported"] + reported["detectedPad"] = "reusableWet" + # fan_speed reads the "disposable" key. + reported["padWetness"] = {"disposable": 1, "reusable": 1} + reported.pop("rankOverlap", None) + reported.update(extra_state) + + with patch("homeassistant.components.roomba.PLATFORMS", [Platform.VACUUM]): + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(ENTITY_ID) + assert state is not None + supported = VacuumEntityFeature(state.attributes["supported_features"]) + assert bool(supported & VacuumEntityFeature.FAN_SPEED) is expect_fan_speed_support + assert state.attributes.get("fan_speed") == expected_fan_speed + + @pytest.mark.parametrize( ("fan_speed", "translation_key"), [ @@ -89,7 +128,10 @@ async def test_braava_set_fan_speed_invalid( translation_key: str, ) -> None: """Test that invalid Braava fan speeds raise instead of being swallowed.""" - mock_roomba.master_state["state"]["reported"]["detectedPad"] = "reusableWet" + reported = mock_roomba.master_state["state"]["reported"] + reported["detectedPad"] = "reusableWet" + # 67 is OVERLAP_STANDARD; without it the mop behavior is not offered at all. + reported["rankOverlap"] = 67 await _setup(hass, mock_config_entry)