Skip to content

Commit 60774c6

Browse files
authored
Add clear shopping list button for Cookidoo (#133583)
* add clear button * set clear button to disabled per default * add actions exception
1 parent c383b41 commit 60774c6

File tree

8 files changed

+218
-2
lines changed

8 files changed

+218
-2
lines changed

homeassistant/components/cookidoo/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from .coordinator import CookidooConfigEntry, CookidooDataUpdateCoordinator
1818

19-
PLATFORMS: list[Platform] = [Platform.TODO]
19+
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.TODO]
2020

2121

2222
async def async_setup_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Support for Cookidoo buttons."""
2+
3+
from collections.abc import Awaitable, Callable
4+
from dataclasses import dataclass
5+
6+
from cookidoo_api import Cookidoo, CookidooException
7+
8+
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
9+
from homeassistant.core import HomeAssistant
10+
from homeassistant.exceptions import HomeAssistantError
11+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
12+
13+
from .const import DOMAIN
14+
from .coordinator import CookidooConfigEntry, CookidooDataUpdateCoordinator
15+
from .entity import CookidooBaseEntity
16+
17+
PARALLEL_UPDATES = 0
18+
19+
20+
@dataclass(frozen=True, kw_only=True)
21+
class CookidooButtonEntityDescription(ButtonEntityDescription):
22+
"""Describes cookidoo button entity."""
23+
24+
press_fn: Callable[[Cookidoo], Awaitable[None]]
25+
26+
27+
TODO_CLEAR = CookidooButtonEntityDescription(
28+
key="todo_clear",
29+
translation_key="todo_clear",
30+
press_fn=lambda client: client.clear_shopping_list(),
31+
entity_registry_enabled_default=False,
32+
)
33+
34+
35+
async def async_setup_entry(
36+
hass: HomeAssistant,
37+
entry: CookidooConfigEntry,
38+
async_add_entities: AddEntitiesCallback,
39+
) -> None:
40+
"""Set up Cookidoo button entities based on a config entry."""
41+
coordinator = entry.runtime_data
42+
43+
async_add_entities([CookidooButton(coordinator, TODO_CLEAR)])
44+
45+
46+
class CookidooButton(CookidooBaseEntity, ButtonEntity):
47+
"""Defines an Cookidoo button."""
48+
49+
entity_description: CookidooButtonEntityDescription
50+
51+
def __init__(
52+
self,
53+
coordinator: CookidooDataUpdateCoordinator,
54+
description: CookidooButtonEntityDescription,
55+
) -> None:
56+
"""Initialize cookidoo button."""
57+
super().__init__(coordinator)
58+
self.entity_description = description
59+
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{description.key}"
60+
61+
async def async_press(self) -> None:
62+
"""Press the button."""
63+
try:
64+
await self.entity_description.press_fn(self.coordinator.cookidoo)
65+
except CookidooException as e:
66+
raise HomeAssistantError(
67+
translation_domain=DOMAIN,
68+
translation_key="button_clear_todo_failed",
69+
) from e
70+
await self.coordinator.async_refresh()

homeassistant/components/cookidoo/icons.json

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
{
22
"entity": {
3+
"button": {
4+
"todo_clear": {
5+
"default": "mdi:cart-off"
6+
}
7+
},
38
"todo": {
49
"ingredient_list": {
510
"default": "mdi:cart-plus"

homeassistant/components/cookidoo/strings.json

+8
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
}
4949
},
5050
"entity": {
51+
"button": {
52+
"todo_clear": {
53+
"name": "Clear shopping list and additional purchases"
54+
}
55+
},
5156
"todo": {
5257
"ingredient_list": {
5358
"name": "Shopping list"
@@ -58,6 +63,9 @@
5863
}
5964
},
6065
"exceptions": {
66+
"button_clear_todo_failed": {
67+
"message": "Failed to clear all items from the Cookidoo shopping list"
68+
},
6169
"todo_save_item_failed": {
6270
"message": "Failed to save {name} to Cookidoo shopping list"
6371
},

tests/components/cookidoo/conftest.py

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def mock_cookidoo_client() -> Generator[AsyncMock]:
5858
"data"
5959
]
6060
]
61+
client.login.return_value = None
6162
yield client
6263

6364

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# serializer version: 1
2+
# name: test_all_entities[button.cookidoo_clear_shopping_list_and_additional_purchases-entry]
3+
EntityRegistryEntrySnapshot({
4+
'aliases': set({
5+
}),
6+
'area_id': None,
7+
'capabilities': None,
8+
'config_entry_id': <ANY>,
9+
'device_class': None,
10+
'device_id': <ANY>,
11+
'disabled_by': None,
12+
'domain': 'button',
13+
'entity_category': None,
14+
'entity_id': 'button.cookidoo_clear_shopping_list_and_additional_purchases',
15+
'has_entity_name': True,
16+
'hidden_by': None,
17+
'icon': None,
18+
'id': <ANY>,
19+
'labels': set({
20+
}),
21+
'name': None,
22+
'options': dict({
23+
}),
24+
'original_device_class': None,
25+
'original_icon': None,
26+
'original_name': 'Clear shopping list and additional purchases',
27+
'platform': 'cookidoo',
28+
'previous_unique_id': None,
29+
'supported_features': 0,
30+
'translation_key': 'todo_clear',
31+
'unique_id': '01JBVVVJ87F6G5V0QJX6HBC94T_todo_clear',
32+
'unit_of_measurement': None,
33+
})
34+
# ---
35+
# name: test_all_entities[button.cookidoo_clear_shopping_list_and_additional_purchases-state]
36+
StateSnapshot({
37+
'attributes': ReadOnlyDict({
38+
'friendly_name': 'Cookidoo Clear shopping list and additional purchases',
39+
}),
40+
'context': <ANY>,
41+
'entity_id': 'button.cookidoo_clear_shopping_list_and_additional_purchases',
42+
'last_changed': <ANY>,
43+
'last_reported': <ANY>,
44+
'last_updated': <ANY>,
45+
'state': 'unknown',
46+
})
47+
# ---
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Tests for the Cookidoo button platform."""
2+
3+
from unittest.mock import AsyncMock, patch
4+
5+
from cookidoo_api import CookidooRequestException
6+
import pytest
7+
from syrupy import SnapshotAssertion
8+
9+
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
10+
from homeassistant.config_entries import ConfigEntryState
11+
from homeassistant.const import ATTR_ENTITY_ID, Platform
12+
from homeassistant.core import HomeAssistant
13+
from homeassistant.exceptions import HomeAssistantError
14+
from homeassistant.helpers import entity_registry as er
15+
16+
from . import setup_integration
17+
18+
from tests.common import MockConfigEntry, snapshot_platform
19+
20+
21+
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
22+
async def test_all_entities(
23+
hass: HomeAssistant,
24+
snapshot: SnapshotAssertion,
25+
mock_cookidoo_client: AsyncMock,
26+
cookidoo_config_entry: MockConfigEntry,
27+
entity_registry: er.EntityRegistry,
28+
) -> None:
29+
"""Test all entities."""
30+
with patch("homeassistant.components.cookidoo.PLATFORMS", [Platform.BUTTON]):
31+
await setup_integration(hass, cookidoo_config_entry)
32+
33+
assert cookidoo_config_entry.state is ConfigEntryState.LOADED
34+
35+
await snapshot_platform(
36+
hass, entity_registry, snapshot, cookidoo_config_entry.entry_id
37+
)
38+
39+
40+
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
41+
async def test_pressing_button(
42+
hass: HomeAssistant,
43+
mock_cookidoo_client: AsyncMock,
44+
cookidoo_config_entry: MockConfigEntry,
45+
) -> None:
46+
"""Test pressing button."""
47+
await setup_integration(hass, cookidoo_config_entry)
48+
49+
await hass.services.async_call(
50+
BUTTON_DOMAIN,
51+
SERVICE_PRESS,
52+
{
53+
ATTR_ENTITY_ID: "button.cookidoo_clear_shopping_list_and_additional_purchases",
54+
},
55+
blocking=True,
56+
)
57+
mock_cookidoo_client.clear_shopping_list.assert_called_once()
58+
59+
60+
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
61+
async def test_pressing_button_exception(
62+
hass: HomeAssistant,
63+
mock_cookidoo_client: AsyncMock,
64+
cookidoo_config_entry: MockConfigEntry,
65+
) -> None:
66+
"""Test pressing button with exception."""
67+
68+
await setup_integration(hass, cookidoo_config_entry)
69+
70+
assert cookidoo_config_entry.state is ConfigEntryState.LOADED
71+
72+
mock_cookidoo_client.clear_shopping_list.side_effect = CookidooRequestException
73+
with pytest.raises(
74+
HomeAssistantError,
75+
match="Failed to clear all items from the Cookidoo shopping list",
76+
):
77+
await hass.services.async_call(
78+
BUTTON_DOMAIN,
79+
SERVICE_PRESS,
80+
{
81+
ATTR_ENTITY_ID: "button.cookidoo_clear_shopping_list_and_additional_purchases",
82+
},
83+
blocking=True,
84+
)

tests/components/cookidoo/test_todo.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ async def test_todo(
5050
) -> None:
5151
"""Snapshot test states of todo platform."""
5252

53-
await setup_integration(hass, cookidoo_config_entry)
53+
with patch("homeassistant.components.cookidoo.PLATFORMS", [Platform.TODO]):
54+
await setup_integration(hass, cookidoo_config_entry)
5455

5556
assert cookidoo_config_entry.state is ConfigEntryState.LOADED
5657

0 commit comments

Comments
 (0)