Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions homeassistant/components/shopping_list/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from homeassistant.util.json import JsonArrayType, load_json_array

from .const import (
ATTR_REVERSE,
DEFAULT_REVERSE,
DOMAIN,
EVENT_SHOPPING_LIST_UPDATED,
SERVICE_ADD_ITEM,
Expand All @@ -27,6 +29,7 @@
SERVICE_INCOMPLETE_ALL,
SERVICE_INCOMPLETE_ITEM,
SERVICE_REMOVE_ITEM,
SERVICE_SORT,
)

ATTR_COMPLETE = "complete"
Expand All @@ -38,6 +41,9 @@

SERVICE_ITEM_SCHEMA = vol.Schema({vol.Required(ATTR_NAME): cv.string})
SERVICE_LIST_SCHEMA = vol.Schema({})
SERVICE_SORT_SCHEMA = vol.Schema(
{vol.Optional(ATTR_REVERSE, default=DEFAULT_REVERSE): bool}
)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
Expand Down Expand Up @@ -111,6 +117,10 @@ async def clear_completed_items_service(call: ServiceCall) -> None:
"""Clear all completed items from the list."""
await data.async_clear_completed()

async def sort_list_service(call: ServiceCall) -> None:
"""Sort all items by name."""
await data.async_sort(call.data[ATTR_REVERSE])

data = hass.data[DOMAIN] = ShoppingData(hass)
await data.async_load()

Expand Down Expand Up @@ -147,6 +157,12 @@ async def clear_completed_items_service(call: ServiceCall) -> None:
clear_completed_items_service,
schema=SERVICE_LIST_SCHEMA,
)
hass.services.async_register(
DOMAIN,
SERVICE_SORT,
sort_list_service,
schema=SERVICE_SORT_SCHEMA,
)

hass.http.register_view(ShoppingListView)
hass.http.register_view(CreateShoppingListItemView)
Expand Down Expand Up @@ -277,6 +293,16 @@ def async_reorder(self, item_ids, context=None):
context=context,
)

async def async_sort(self, reverse=False, context=None):
"""Sort items by name."""
self.items = sorted(self.items, key=lambda item: item["name"], reverse=reverse)
self.hass.async_add_executor_job(self.save)
self.hass.bus.async_fire(
EVENT_SHOPPING_LIST_UPDATED,
{"action": "sorted"},
context=context,
)

async def async_load(self) -> None:
"""Load items."""

Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/shopping_list/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
DOMAIN = "shopping_list"
EVENT_SHOPPING_LIST_UPDATED = "shopping_list_updated"

ATTR_REVERSE = "reverse"

DEFAULT_REVERSE = False

SERVICE_ADD_ITEM = "add_item"
SERVICE_REMOVE_ITEM = "remove_item"
SERVICE_COMPLETE_ITEM = "complete_item"
SERVICE_INCOMPLETE_ITEM = "incomplete_item"
SERVICE_COMPLETE_ALL = "complete_all"
SERVICE_INCOMPLETE_ALL = "incomplete_all"
SERVICE_CLEAR_COMPLETED_ITEMS = "clear_completed_items"
SERVICE_SORT = "sort"
11 changes: 11 additions & 0 deletions homeassistant/components/shopping_list/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,14 @@ incomplete_all:
clear_completed_items:
name: Clear completed items
description: Clear completed items from the shopping list.

sort:
name: Sort all items
description: Sort all items by name in the shopping list.
fields:
reverse:
name: Sort reverse
description: Whether to sort in reverse (descending) order.
default: false
selector:
boolean:
51 changes: 41 additions & 10 deletions tests/components/shopping_list/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

from homeassistant.components.shopping_list import NoMatchingShoppingListItem
from homeassistant.components.shopping_list.const import (
ATTR_REVERSE,
DOMAIN,
EVENT_SHOPPING_LIST_UPDATED,
SERVICE_ADD_ITEM,
SERVICE_CLEAR_COMPLETED_ITEMS,
SERVICE_COMPLETE_ITEM,
SERVICE_REMOVE_ITEM,
SERVICE_SORT,
)
from homeassistant.components.websocket_api.const import (
ERR_INVALID_FORMAT,
Expand Down Expand Up @@ -657,8 +659,6 @@ async def test_add_item_service(hass: HomeAssistant, sl_setup) -> None:
{ATTR_NAME: "beer"},
blocking=True,
)
await hass.async_block_till_done()

assert len(hass.data[DOMAIN].items) == 1
assert len(events) == 1

Expand All @@ -672,15 +672,12 @@ async def test_remove_item_service(hass: HomeAssistant, sl_setup) -> None:
{ATTR_NAME: "beer"},
blocking=True,
)
await hass.async_block_till_done()
await hass.services.async_call(
DOMAIN,
SERVICE_ADD_ITEM,
{ATTR_NAME: "cheese"},
blocking=True,
)
await hass.async_block_till_done()

assert len(hass.data[DOMAIN].items) == 2
assert len(events) == 2

Expand All @@ -690,8 +687,6 @@ async def test_remove_item_service(hass: HomeAssistant, sl_setup) -> None:
{ATTR_NAME: "beer"},
blocking=True,
)
await hass.async_block_till_done()

assert len(hass.data[DOMAIN].items) == 1
assert hass.data[DOMAIN].items[0]["name"] == "cheese"
assert len(events) == 3
Expand All @@ -706,7 +701,6 @@ async def test_clear_completed_items_service(hass: HomeAssistant, sl_setup) -> N
{ATTR_NAME: "beer"},
blocking=True,
)
await hass.async_block_till_done()
assert len(hass.data[DOMAIN].items) == 1
assert len(events) == 1

Expand All @@ -717,7 +711,6 @@ async def test_clear_completed_items_service(hass: HomeAssistant, sl_setup) -> N
{ATTR_NAME: "beer"},
blocking=True,
)
await hass.async_block_till_done()
assert len(hass.data[DOMAIN].items) == 1
assert len(events) == 1

Expand All @@ -728,6 +721,44 @@ async def test_clear_completed_items_service(hass: HomeAssistant, sl_setup) -> N
{},
blocking=True,
)
await hass.async_block_till_done()
assert len(hass.data[DOMAIN].items) == 0
assert len(events) == 1


async def test_sort_list_service(hass: HomeAssistant, sl_setup) -> None:
"""Test sort_all service."""

for name in ("zzz", "ddd", "aaa"):
await hass.services.async_call(
DOMAIN,
SERVICE_ADD_ITEM,
{ATTR_NAME: name},
blocking=True,
)

# sort ascending
events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED)
await hass.services.async_call(
DOMAIN,
SERVICE_SORT,
{ATTR_REVERSE: False},
blocking=True,
)

assert hass.data[DOMAIN].items[0][ATTR_NAME] == "aaa"
assert hass.data[DOMAIN].items[1][ATTR_NAME] == "ddd"
assert hass.data[DOMAIN].items[2][ATTR_NAME] == "zzz"
assert len(events) == 1

# sort descending
await hass.services.async_call(
DOMAIN,
SERVICE_SORT,
{ATTR_REVERSE: True},
blocking=True,
)

assert hass.data[DOMAIN].items[0][ATTR_NAME] == "zzz"
assert hass.data[DOMAIN].items[1][ATTR_NAME] == "ddd"
assert hass.data[DOMAIN].items[2][ATTR_NAME] == "aaa"
assert len(events) == 2