diff --git a/.core_files.yaml b/.core_files.yaml index 4ac65cd92c7..6fbfdf90a4b 100644 --- a/.core_files.yaml +++ b/.core_files.yaml @@ -30,6 +30,7 @@ base_platforms: &base_platforms - homeassistant/components/humidifier/** - homeassistant/components/image/** - homeassistant/components/image_processing/** + - homeassistant/components/lawn_mower/** - homeassistant/components/light/** - homeassistant/components/lock/** - homeassistant/components/media_player/** diff --git a/.strict-typing b/.strict-typing index c56c7d9f137..f83260c383f 100644 --- a/.strict-typing +++ b/.strict-typing @@ -194,6 +194,7 @@ homeassistant.components.lacrosse.* homeassistant.components.lacrosse_view.* homeassistant.components.lametric.* homeassistant.components.laundrify.* +homeassistant.components.lawn_mower.* homeassistant.components.lcn.* homeassistant.components.ld2410_ble.* homeassistant.components.lidarr.* diff --git a/CODEOWNERS b/CODEOWNERS index 812caea4da5..427c8290b60 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -673,6 +673,8 @@ build.json @home-assistant/supervisor /tests/components/launch_library/ @ludeeus @DurgNomis-drol /homeassistant/components/laundrify/ @xLarry /tests/components/laundrify/ @xLarry +/homeassistant/components/lawn_mower/ @home-assistant/core +/tests/components/lawn_mower/ @home-assistant/core /homeassistant/components/lcn/ @alengwenus /tests/components/lcn/ @alengwenus /homeassistant/components/ld2410_ble/ @930913 diff --git a/homeassistant/components/kitchen_sink/__init__.py b/homeassistant/components/kitchen_sink/__init__.py index a85221108f8..5c8088823b2 100644 --- a/homeassistant/components/kitchen_sink/__init__.py +++ b/homeassistant/components/kitchen_sink/__init__.py @@ -27,9 +27,10 @@ DOMAIN = "kitchen_sink" COMPONENTS_WITH_DEMO_PLATFORM = [ - Platform.SENSOR, - Platform.LOCK, Platform.IMAGE, + Platform.LAWN_MOWER, + Platform.LOCK, + Platform.SENSOR, Platform.WEATHER, ] diff --git a/homeassistant/components/kitchen_sink/lawn_mower.py b/homeassistant/components/kitchen_sink/lawn_mower.py new file mode 100644 index 00000000000..119b37b7569 --- /dev/null +++ b/homeassistant/components/kitchen_sink/lawn_mower.py @@ -0,0 +1,100 @@ +"""Demo platform that has a couple fake lawn mowers.""" +from __future__ import annotations + +from homeassistant.components.lawn_mower import ( + LawnMowerActivity, + LawnMowerEntity, + LawnMowerEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + + +async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, +) -> None: + """Set up the Demo lawn mowers.""" + async_add_entities( + [ + DemoLawnMower( + "kitchen_sink_mower_001", + "Mower can mow", + LawnMowerActivity.DOCKED, + LawnMowerEntityFeature.START_MOWING, + ), + DemoLawnMower( + "kitchen_sink_mower_002", + "Mower can dock", + LawnMowerActivity.MOWING, + LawnMowerEntityFeature.DOCK | LawnMowerEntityFeature.START_MOWING, + ), + DemoLawnMower( + "kitchen_sink_mower_003", + "Mower can pause", + LawnMowerActivity.DOCKED, + LawnMowerEntityFeature.PAUSE | LawnMowerEntityFeature.START_MOWING, + ), + DemoLawnMower( + "kitchen_sink_mower_004", + "Mower can do all", + LawnMowerActivity.DOCKED, + LawnMowerEntityFeature.DOCK + | LawnMowerEntityFeature.PAUSE + | LawnMowerEntityFeature.START_MOWING, + ), + DemoLawnMower( + "kitchen_sink_mower_005", + "Mower is paused", + LawnMowerActivity.PAUSED, + LawnMowerEntityFeature.DOCK + | LawnMowerEntityFeature.PAUSE + | LawnMowerEntityFeature.START_MOWING, + ), + ] + ) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Everything but the Kitchen Sink config entry.""" + await async_setup_platform(hass, {}, async_add_entities) + + +class DemoLawnMower(LawnMowerEntity): + """Representation of a Demo lawn mower.""" + + def __init__( + self, + unique_id: str, + name: str, + activity: LawnMowerActivity, + features: LawnMowerEntityFeature = LawnMowerEntityFeature(0), + ) -> None: + """Initialize the lawn mower.""" + self._attr_name = name + self._attr_unique_id = unique_id + self._attr_supported_features = features + self._attr_activity = activity + + async def async_start_mowing(self) -> None: + """Start mowing.""" + self._attr_activity = LawnMowerActivity.MOWING + self.async_write_ha_state() + + async def async_dock(self) -> None: + """Start docking.""" + self._attr_activity = LawnMowerActivity.DOCKED + self.async_write_ha_state() + + async def async_pause(self) -> None: + """Pause mower.""" + self._attr_activity = LawnMowerActivity.PAUSED + self.async_write_ha_state() diff --git a/homeassistant/components/lawn_mower/__init__.py b/homeassistant/components/lawn_mower/__init__.py new file mode 100644 index 00000000000..5388463316f --- /dev/null +++ b/homeassistant/components/lawn_mower/__init__.py @@ -0,0 +1,120 @@ +"""The lawn mower integration.""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import timedelta +import logging +from typing import final + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_validation import ( # noqa: F401 + PLATFORM_SCHEMA, + PLATFORM_SCHEMA_BASE, +) +from homeassistant.helpers.entity import Entity, EntityDescription +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.typing import ConfigType + +from .const import ( + DOMAIN, + SERVICE_DOCK, + SERVICE_PAUSE, + SERVICE_START_MOWING, + LawnMowerActivity, + LawnMowerEntityFeature, +) + +SCAN_INTERVAL = timedelta(seconds=60) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the lawn_mower component.""" + component = hass.data[DOMAIN] = EntityComponent[LawnMowerEntity]( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL + ) + await component.async_setup(config) + + component.async_register_entity_service( + SERVICE_START_MOWING, + {}, + "async_start_mowing", + [LawnMowerEntityFeature.START_MOWING], + ) + component.async_register_entity_service( + SERVICE_PAUSE, {}, "async_pause", [LawnMowerEntityFeature.PAUSE] + ) + component.async_register_entity_service( + SERVICE_DOCK, {}, "async_dock", [LawnMowerEntityFeature.DOCK] + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up lawn mower devices.""" + component: EntityComponent[LawnMowerEntity] = hass.data[DOMAIN] + return await component.async_setup_entry(entry) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + component: EntityComponent[LawnMowerEntity] = hass.data[DOMAIN] + return await component.async_unload_entry(entry) + + +@dataclass +class LawnMowerEntityEntityDescription(EntityDescription): + """A class that describes lawn mower entities.""" + + +class LawnMowerEntity(Entity): + """Base class for lawn mower entities.""" + + entity_description: LawnMowerEntityEntityDescription + _attr_activity: LawnMowerActivity | None = None + _attr_supported_features: LawnMowerEntityFeature = LawnMowerEntityFeature(0) + + @final + @property + def state(self) -> str | None: + """Return the current state.""" + if (activity := self.activity) is None: + return None + return str(activity) + + @property + def activity(self) -> LawnMowerActivity | None: + """Return the current lawn mower activity.""" + return self._attr_activity + + @property + def supported_features(self) -> LawnMowerEntityFeature: + """Flag lawn mower features that are supported.""" + return self._attr_supported_features + + def start_mowing(self) -> None: + """Start or resume mowing.""" + raise NotImplementedError() + + async def async_start_mowing(self) -> None: + """Start or resume mowing.""" + await self.hass.async_add_executor_job(self.start_mowing) + + def dock(self) -> None: + """Dock the mower.""" + raise NotImplementedError() + + async def async_dock(self) -> None: + """Dock the mower.""" + await self.hass.async_add_executor_job(self.dock) + + def pause(self) -> None: + """Pause the lawn mower.""" + raise NotImplementedError() + + async def async_pause(self) -> None: + """Pause the lawn mower.""" + await self.hass.async_add_executor_job(self.pause) diff --git a/homeassistant/components/lawn_mower/const.py b/homeassistant/components/lawn_mower/const.py new file mode 100644 index 00000000000..706c9616450 --- /dev/null +++ b/homeassistant/components/lawn_mower/const.py @@ -0,0 +1,33 @@ +"""Constants for the lawn mower integration.""" +from enum import IntFlag, StrEnum + + +class LawnMowerActivity(StrEnum): + """Activity state of lawn mower devices.""" + + ERROR = "error" + """Device is in error state, needs assistance.""" + + PAUSED = "paused" + """Paused during activity.""" + + MOWING = "mowing" + """Device is mowing.""" + + DOCKED = "docked" + """Device is docked.""" + + +class LawnMowerEntityFeature(IntFlag): + """Supported features of the lawn mower entity.""" + + START_MOWING = 1 + PAUSE = 2 + DOCK = 4 + + +DOMAIN = "lawn_mower" + +SERVICE_START_MOWING = "start_mowing" +SERVICE_PAUSE = "pause" +SERVICE_DOCK = "dock" diff --git a/homeassistant/components/lawn_mower/manifest.json b/homeassistant/components/lawn_mower/manifest.json new file mode 100644 index 00000000000..43418a9440d --- /dev/null +++ b/homeassistant/components/lawn_mower/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "lawn_mower", + "name": "Lawn Mower", + "codeowners": ["@home-assistant/core"], + "documentation": "https://www.home-assistant.io/integrations/lawn_mower", + "integration_type": "entity", + "quality_scale": "internal" +} diff --git a/homeassistant/components/lawn_mower/services.yaml b/homeassistant/components/lawn_mower/services.yaml new file mode 100644 index 00000000000..8c9a2f1adcc --- /dev/null +++ b/homeassistant/components/lawn_mower/services.yaml @@ -0,0 +1,22 @@ +# Describes the format for available lawn_mower services + +start_mowing: + target: + entity: + domain: lawn_mower + supported_features: + - lawn_mower.LawnMowerEntityFeature.START_MOWING + +dock: + target: + entity: + domain: lawn_mower + supported_features: + - lawn_mower.LawnMowerEntityFeature.DOCK + +pause: + target: + entity: + domain: lawn_mower + supported_features: + - lawn_mower.LawnMowerEntityFeature.PAUSE diff --git a/homeassistant/components/lawn_mower/strings.json b/homeassistant/components/lawn_mower/strings.json new file mode 100644 index 00000000000..caf2e15df77 --- /dev/null +++ b/homeassistant/components/lawn_mower/strings.json @@ -0,0 +1,28 @@ +{ + "title": "Lawn mower", + "entity_component": { + "_": { + "name": "[%key:component::lawn_mower::title%]", + "state": { + "error": "Error", + "paused": "Paused", + "mowing": "Mowing", + "docked": "Docked" + } + } + }, + "services": { + "start_mowing": { + "name": "Start mowing", + "description": "Starts the mowing task." + }, + "dock": { + "name": "Return to dock", + "description": "Stops the mowing task and returns to the dock." + }, + "pause": { + "name": "Pause", + "description": "Pauses the mowing task." + } + } +} diff --git a/homeassistant/const.py b/homeassistant/const.py index adca3dc965c..66d05f0bd4f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -39,6 +39,7 @@ class Platform(StrEnum): HUMIDIFIER = "humidifier" IMAGE = "image" IMAGE_PROCESSING = "image_processing" + LAWN_MOWER = "lawn_mower" LIGHT = "light" LOCK = "lock" MAILBOX = "mailbox" diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index ba473758121..efb1ee0b1f1 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -92,6 +92,7 @@ def _entity_features() -> dict[str, type[IntFlag]]: from homeassistant.components.cover import CoverEntityFeature from homeassistant.components.fan import FanEntityFeature from homeassistant.components.humidifier import HumidifierEntityFeature + from homeassistant.components.lawn_mower import LawnMowerEntityFeature from homeassistant.components.light import LightEntityFeature from homeassistant.components.lock import LockEntityFeature from homeassistant.components.media_player import MediaPlayerEntityFeature @@ -110,6 +111,7 @@ def _entity_features() -> dict[str, type[IntFlag]]: "CoverEntityFeature": CoverEntityFeature, "FanEntityFeature": FanEntityFeature, "HumidifierEntityFeature": HumidifierEntityFeature, + "LawnMowerEntityFeature": LawnMowerEntityFeature, "LightEntityFeature": LightEntityFeature, "LockEntityFeature": LockEntityFeature, "MediaPlayerEntityFeature": MediaPlayerEntityFeature, diff --git a/mypy.ini b/mypy.ini index 1c47ad019a2..233539589cb 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1702,6 +1702,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.lawn_mower.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.lcn.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/tests/components/kitchen_sink/snapshots/test_lawn_mower.ambr b/tests/components/kitchen_sink/snapshots/test_lawn_mower.ambr new file mode 100644 index 00000000000..879e78d5534 --- /dev/null +++ b/tests/components/kitchen_sink/snapshots/test_lawn_mower.ambr @@ -0,0 +1,60 @@ +# serializer version: 1 +# name: test_states + set({ + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mower can do all', + 'supported_features': , + }), + 'context': , + 'entity_id': 'lawn_mower.mower_can_do_all', + 'last_changed': , + 'last_updated': , + 'state': 'docked', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mower can dock', + 'supported_features': , + }), + 'context': , + 'entity_id': 'lawn_mower.mower_can_dock', + 'last_changed': , + 'last_updated': , + 'state': 'mowing', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mower can mow', + 'supported_features': , + }), + 'context': , + 'entity_id': 'lawn_mower.mower_can_mow', + 'last_changed': , + 'last_updated': , + 'state': 'docked', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mower can pause', + 'supported_features': , + }), + 'context': , + 'entity_id': 'lawn_mower.mower_can_pause', + 'last_changed': , + 'last_updated': , + 'state': 'docked', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mower is paused', + 'supported_features': , + }), + 'context': , + 'entity_id': 'lawn_mower.mower_is_paused', + 'last_changed': , + 'last_updated': , + 'state': 'paused', + }), + }) +# --- diff --git a/tests/components/kitchen_sink/test_lawn_mower.py b/tests/components/kitchen_sink/test_lawn_mower.py new file mode 100644 index 00000000000..efd1b7485ab --- /dev/null +++ b/tests/components/kitchen_sink/test_lawn_mower.py @@ -0,0 +1,116 @@ +"""The tests for the kitchen_sink lawn mower platform.""" +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.kitchen_sink import DOMAIN +from homeassistant.components.lawn_mower import ( + DOMAIN as LAWN_MOWER_DOMAIN, + SERVICE_DOCK, + SERVICE_PAUSE, + SERVICE_START_MOWING, + LawnMowerActivity, +) +from homeassistant.const import ATTR_ENTITY_ID, EVENT_STATE_CHANGED, Platform +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import async_capture_events, async_mock_service + +MOWER_SERVICE_ENTITY = "lawn_mower.mower_can_dock" + + +@pytest.fixture +async def lawn_mower_only() -> None: + """Enable only the lawn mower platform.""" + with patch( + "homeassistant.components.kitchen_sink.COMPONENTS_WITH_DEMO_PLATFORM", + [Platform.LAWN_MOWER], + ): + yield + + +@pytest.fixture(autouse=True) +async def setup_comp(hass: HomeAssistant, lawn_mower_only): + """Set up demo component.""" + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + + +async def test_states(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None: + """Test the expected lawn mower entities are added.""" + states = hass.states.async_all() + assert set(states) == snapshot + + +@pytest.mark.parametrize( + ("entity", "service_call", "activity", "next_activity"), + [ + ( + "lawn_mower.mower_can_mow", + SERVICE_START_MOWING, + LawnMowerActivity.DOCKED, + LawnMowerActivity.MOWING, + ), + ( + "lawn_mower.mower_can_pause", + SERVICE_PAUSE, + LawnMowerActivity.DOCKED, + LawnMowerActivity.PAUSED, + ), + ( + "lawn_mower.mower_is_paused", + SERVICE_START_MOWING, + LawnMowerActivity.PAUSED, + LawnMowerActivity.MOWING, + ), + ( + "lawn_mower.mower_can_dock", + SERVICE_DOCK, + LawnMowerActivity.MOWING, + LawnMowerActivity.DOCKED, + ), + ], +) +async def test_mower( + hass: HomeAssistant, + entity: str, + service_call: str, + activity: LawnMowerActivity, + next_activity: LawnMowerActivity, +) -> None: + """Test the activity states of a lawn mower.""" + state = hass.states.get(entity) + + assert state.state == str(activity.value) + await hass.async_block_till_done() + + state_changes = async_capture_events(hass, EVENT_STATE_CHANGED) + await hass.services.async_call( + LAWN_MOWER_DOMAIN, service_call, {ATTR_ENTITY_ID: entity}, blocking=False + ) + await hass.async_block_till_done() + + assert state_changes[0].data["entity_id"] == entity + assert state_changes[0].data["new_state"].state == str(next_activity.value) + + +@pytest.mark.parametrize( + "service_call", + [ + SERVICE_DOCK, + SERVICE_START_MOWING, + SERVICE_PAUSE, + ], +) +async def test_service_calls_mocked(hass: HomeAssistant, service_call) -> None: + """Test the services of a lawn mower.""" + calls = async_mock_service(hass, LAWN_MOWER_DOMAIN, service_call) + await hass.services.async_call( + LAWN_MOWER_DOMAIN, + service_call, + {ATTR_ENTITY_ID: MOWER_SERVICE_ENTITY}, + blocking=True, + ) + assert len(calls) == 1 diff --git a/tests/components/lawn_mower/__init__.py b/tests/components/lawn_mower/__init__.py new file mode 100644 index 00000000000..0f96921206e --- /dev/null +++ b/tests/components/lawn_mower/__init__.py @@ -0,0 +1 @@ +"""Tests for the lawn mower integration.""" diff --git a/tests/components/lawn_mower/test_init.py b/tests/components/lawn_mower/test_init.py new file mode 100644 index 00000000000..39d594e1e17 --- /dev/null +++ b/tests/components/lawn_mower/test_init.py @@ -0,0 +1,178 @@ +"""The tests for the lawn mower integration.""" +from collections.abc import Generator +from unittest.mock import MagicMock + +import pytest + +from homeassistant.components.lawn_mower import ( + DOMAIN as LAWN_MOWER_DOMAIN, + LawnMowerActivity, + LawnMowerEntity, + LawnMowerEntityFeature, +) +from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow +from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from tests.common import ( + MockConfigEntry, + MockModule, + MockPlatform, + mock_config_flow, + mock_integration, + mock_platform, +) + +TEST_DOMAIN = "test" + + +class MockFlow(ConfigFlow): + """Test flow.""" + + +class MockLawnMowerEntity(LawnMowerEntity): + """Mock lawn mower device to use in tests.""" + + def __init__( + self, + unique_id: str = "mock_lawn_mower", + name: str = "Lawn Mower", + features: LawnMowerEntityFeature = LawnMowerEntityFeature(0), + ) -> None: + """Initialize the lawn mower.""" + self._attr_name = name + self._attr_unique_id = unique_id + self._attr_supported_features = features + + def start_mowing(self) -> None: + """Start mowing.""" + self._attr_activity = LawnMowerActivity.MOWING + + +@pytest.fixture(autouse=True) +def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]: + """Mock config flow.""" + mock_platform(hass, f"{TEST_DOMAIN}.config_flow") + + with mock_config_flow(TEST_DOMAIN, MockFlow): + yield + + +async def test_lawn_mower_setup(hass: HomeAssistant) -> None: + """Test setup and tear down of lawn mower platform and entity.""" + + async def async_setup_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Set up test config entry.""" + await hass.config_entries.async_forward_entry_setup( + config_entry, Platform.LAWN_MOWER + ) + return True + + async def async_unload_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Unload up test config entry.""" + await hass.config_entries.async_unload_platforms( + config_entry, [Platform.LAWN_MOWER] + ) + return True + + mock_platform(hass, f"{TEST_DOMAIN}.config_flow") + mock_integration( + hass, + MockModule( + TEST_DOMAIN, + async_setup_entry=async_setup_entry_init, + async_unload_entry=async_unload_entry_init, + ), + ) + + entity1 = MockLawnMowerEntity() + entity1.entity_id = "lawn_mower.mock_lawn_mower" + + async def async_setup_entry_platform( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + ) -> None: + """Set up test platform via config entry.""" + async_add_entities([entity1]) + + mock_platform( + hass, + f"{TEST_DOMAIN}.{LAWN_MOWER_DOMAIN}", + MockPlatform(async_setup_entry=async_setup_entry_platform), + ) + + config_entry = MockConfigEntry(domain=TEST_DOMAIN) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ConfigEntryState.LOADED + assert hass.states.get(entity1.entity_id) + + assert await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state == ConfigEntryState.NOT_LOADED + entity_state = hass.states.get(entity1.entity_id) + + assert entity_state + assert entity_state.state == STATE_UNAVAILABLE + + +async def test_sync_start_mowing(hass: HomeAssistant) -> None: + """Test if async mowing calls sync mowing.""" + lawn_mower = MockLawnMowerEntity() + lawn_mower.hass = hass + + lawn_mower.start_mowing = MagicMock() + await lawn_mower.async_start_mowing() + + assert lawn_mower.start_mowing.called + + +async def test_sync_dock(hass: HomeAssistant) -> None: + """Test if async dock calls sync dock.""" + lawn_mower = MockLawnMowerEntity() + lawn_mower.hass = hass + + lawn_mower.dock = MagicMock() + await lawn_mower.async_dock() + + assert lawn_mower.dock.called + + +async def test_sync_pause(hass: HomeAssistant) -> None: + """Test if async pause calls sync pause.""" + lawn_mower = MockLawnMowerEntity() + lawn_mower.hass = hass + + lawn_mower.pause = MagicMock() + await lawn_mower.async_pause() + + assert lawn_mower.pause.called + + +async def test_lawn_mower_default(hass: HomeAssistant) -> None: + """Test lawn mower entity with defaults.""" + lawn_mower = MockLawnMowerEntity() + lawn_mower.hass = hass + + assert lawn_mower.state is None + + +async def test_lawn_mower_state(hass: HomeAssistant) -> None: + """Test lawn mower entity returns state.""" + lawn_mower = MockLawnMowerEntity( + "lawn_mower_1", "Test lawn mower", LawnMowerActivity.MOWING + ) + lawn_mower.hass = hass + lawn_mower.start_mowing() + + assert lawn_mower.state == str(LawnMowerActivity.MOWING)