Add lawnmower entity (#93623)
Co-authored-by: Franck Nijhof <frenck@frenck.nl> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/98744/head
parent
538de6d1f3
commit
82b3ced4f1
|
@ -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/**
|
||||
|
|
|
@ -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.*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
|
@ -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"
|
|
@ -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"
|
||||
}
|
|
@ -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
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
10
mypy.ini
10
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
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# serializer version: 1
|
||||
# name: test_states
|
||||
set({
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mower can do all',
|
||||
'supported_features': <LawnMowerEntityFeature: 7>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'lawn_mower.mower_can_do_all',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'docked',
|
||||
}),
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mower can dock',
|
||||
'supported_features': <LawnMowerEntityFeature: 5>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'lawn_mower.mower_can_dock',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'mowing',
|
||||
}),
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mower can mow',
|
||||
'supported_features': <LawnMowerEntityFeature: 1>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'lawn_mower.mower_can_mow',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'docked',
|
||||
}),
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mower can pause',
|
||||
'supported_features': <LawnMowerEntityFeature: 3>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'lawn_mower.mower_can_pause',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'docked',
|
||||
}),
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mower is paused',
|
||||
'supported_features': <LawnMowerEntityFeature: 7>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'lawn_mower.mower_is_paused',
|
||||
'last_changed': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'paused',
|
||||
}),
|
||||
})
|
||||
# ---
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
"""Tests for the lawn mower integration."""
|
|
@ -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)
|
Loading…
Reference in New Issue