Add support for reordering Shopping List Items via Drag and Drop (#41585)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>pull/44840/head
parent
addafd517f
commit
c654476e24
|
@ -128,6 +128,8 @@ async def async_setup_entry(hass, config_entry):
|
|||
SCHEMA_WEBSOCKET_CLEAR_ITEMS,
|
||||
)
|
||||
|
||||
websocket_api.async_register_command(hass, websocket_handle_reorder)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -163,6 +165,31 @@ class ShoppingData:
|
|||
self.items = [itm for itm in self.items if not itm["complete"]]
|
||||
await self.hass.async_add_executor_job(self.save)
|
||||
|
||||
@callback
|
||||
def async_reorder(self, item_ids):
|
||||
"""Reorder items."""
|
||||
# The array for sorted items.
|
||||
new_items = []
|
||||
all_items_mapping = {item["id"]: item for item in self.items}
|
||||
# Append items by the order of passed in array.
|
||||
for item_id in item_ids:
|
||||
if item_id not in all_items_mapping:
|
||||
raise KeyError
|
||||
new_items.append(all_items_mapping[item_id])
|
||||
# Remove the item from mapping after it's appended in the result array.
|
||||
del all_items_mapping[item_id]
|
||||
# Append the rest of the items
|
||||
for key in all_items_mapping:
|
||||
# All the unchecked items must be passed in the item_ids array,
|
||||
# so all items left in the mapping should be checked items.
|
||||
if all_items_mapping[key]["complete"] is False:
|
||||
raise vol.Invalid(
|
||||
"The item ids array doesn't contain all the unchecked shopping list items."
|
||||
)
|
||||
new_items.append(all_items_mapping[key])
|
||||
self.items = new_items
|
||||
self.hass.async_add_executor_job(self.save)
|
||||
|
||||
async def async_load(self):
|
||||
"""Load items."""
|
||||
|
||||
|
@ -277,3 +304,26 @@ async def websocket_handle_clear(hass, connection, msg):
|
|||
await hass.data[DOMAIN].async_clear_completed()
|
||||
hass.bus.async_fire(EVENT, {"action": "clear"})
|
||||
connection.send_message(websocket_api.result_message(msg["id"]))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "shopping_list/items/reorder",
|
||||
vol.Required("item_ids"): [str],
|
||||
}
|
||||
)
|
||||
def websocket_handle_reorder(hass, connection, msg):
|
||||
"""Handle reordering shopping_list items."""
|
||||
msg_id = msg.pop("id")
|
||||
try:
|
||||
hass.data[DOMAIN].async_reorder(msg.pop("item_ids"))
|
||||
hass.bus.async_fire(EVENT, {"action": "reorder"})
|
||||
connection.send_result(msg_id)
|
||||
except KeyError:
|
||||
connection.send_error(
|
||||
msg_id,
|
||||
websocket_api.const.ERR_NOT_FOUND,
|
||||
"One or more item id(s) not found.",
|
||||
)
|
||||
except vol.Invalid as err:
|
||||
connection.send_error(msg_id, websocket_api.const.ERR_INVALID_FORMAT, f"{err}")
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
"""Test shopping list component."""
|
||||
|
||||
from homeassistant.components.websocket_api.const import TYPE_RESULT
|
||||
from homeassistant.components.websocket_api.const import (
|
||||
ERR_INVALID_FORMAT,
|
||||
ERR_NOT_FOUND,
|
||||
TYPE_RESULT,
|
||||
)
|
||||
from homeassistant.const import HTTP_NOT_FOUND
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
|
@ -311,3 +315,125 @@ async def test_ws_add_item_fail(hass, hass_ws_client, sl_setup):
|
|||
msg = await client.receive_json()
|
||||
assert msg["success"] is False
|
||||
assert len(hass.data["shopping_list"].items) == 0
|
||||
|
||||
|
||||
async def test_ws_reorder_items(hass, hass_ws_client, sl_setup):
|
||||
"""Test reordering shopping_list items websocket command."""
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "apple"}}
|
||||
)
|
||||
|
||||
beer_id = hass.data["shopping_list"].items[0]["id"]
|
||||
wine_id = hass.data["shopping_list"].items[1]["id"]
|
||||
apple_id = hass.data["shopping_list"].items[2]["id"]
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 6,
|
||||
"type": "shopping_list/items/reorder",
|
||||
"item_ids": [wine_id, apple_id, beer_id],
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["success"] is True
|
||||
assert hass.data["shopping_list"].items[0] == {
|
||||
"id": wine_id,
|
||||
"name": "wine",
|
||||
"complete": False,
|
||||
}
|
||||
assert hass.data["shopping_list"].items[1] == {
|
||||
"id": apple_id,
|
||||
"name": "apple",
|
||||
"complete": False,
|
||||
}
|
||||
assert hass.data["shopping_list"].items[2] == {
|
||||
"id": beer_id,
|
||||
"name": "beer",
|
||||
"complete": False,
|
||||
}
|
||||
|
||||
# Mark wine as completed.
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 7,
|
||||
"type": "shopping_list/items/update",
|
||||
"item_id": wine_id,
|
||||
"complete": True,
|
||||
}
|
||||
)
|
||||
_ = await client.receive_json()
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 8,
|
||||
"type": "shopping_list/items/reorder",
|
||||
"item_ids": [apple_id, beer_id],
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["success"] is True
|
||||
assert hass.data["shopping_list"].items[0] == {
|
||||
"id": apple_id,
|
||||
"name": "apple",
|
||||
"complete": False,
|
||||
}
|
||||
assert hass.data["shopping_list"].items[1] == {
|
||||
"id": beer_id,
|
||||
"name": "beer",
|
||||
"complete": False,
|
||||
}
|
||||
assert hass.data["shopping_list"].items[2] == {
|
||||
"id": wine_id,
|
||||
"name": "wine",
|
||||
"complete": True,
|
||||
}
|
||||
|
||||
|
||||
async def test_ws_reorder_items_failure(hass, hass_ws_client, sl_setup):
|
||||
"""Test reordering shopping_list items websocket command."""
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "apple"}}
|
||||
)
|
||||
|
||||
beer_id = hass.data["shopping_list"].items[0]["id"]
|
||||
wine_id = hass.data["shopping_list"].items[1]["id"]
|
||||
apple_id = hass.data["shopping_list"].items[2]["id"]
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
# Testing sending bad item id.
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 8,
|
||||
"type": "shopping_list/items/reorder",
|
||||
"item_ids": [wine_id, apple_id, beer_id, "BAD_ID"],
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["success"] is False
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
|
||||
# Testing not sending all unchecked item ids.
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 9,
|
||||
"type": "shopping_list/items/reorder",
|
||||
"item_ids": [wine_id, apple_id],
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["success"] is False
|
||||
assert msg["error"]["code"] == ERR_INVALID_FORMAT
|
||||
|
|
Loading…
Reference in New Issue