Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
59 changes: 59 additions & 0 deletions homeassistant/components/shopping_list/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
WS_TYPE_SHOPPING_LIST_ADD_ITEM = "shopping_list/items/add"
WS_TYPE_SHOPPING_LIST_UPDATE_ITEM = "shopping_list/items/update"
WS_TYPE_SHOPPING_LIST_CLEAR_ITEMS = "shopping_list/items/clear"
WS_TYPE_SHOPPING_LIST_REORDER_ITEMS = "shopping_list/items/reorder"

SCHEMA_WEBSOCKET_ITEMS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_SHOPPING_LIST_ITEMS}
Expand All @@ -53,6 +54,13 @@
{vol.Required("type"): WS_TYPE_SHOPPING_LIST_CLEAR_ITEMS}
)

SCHEMA_WEBSOCKET_REORDER_ITEMS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_SHOPPING_LIST_REORDER_ITEMS,
vol.Required("item_ids"): [str],
}
)


async def async_setup(hass, config):
"""Initialize the shopping list."""
Expand Down Expand Up @@ -128,6 +136,12 @@ async def complete_item_service(call):
SCHEMA_WEBSOCKET_CLEAR_ITEMS,
)

hass.components.websocket_api.async_register_command(
WS_TYPE_SHOPPING_LIST_REORDER_ITEMS,
websocket_handle_reorder,
SCHEMA_WEBSOCKET_REORDER_ITEMS,
)

return True


Expand Down Expand Up @@ -166,6 +180,31 @@ def async_clear_completed(self):
self.items = [itm for itm in self.items if not itm["complete"]]
self.hass.async_add_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_job(self.save)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use async_add_executor_job

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


async def async_load(self):
"""Load items."""

Expand Down Expand Up @@ -281,3 +320,23 @@ def websocket_handle_clear(hass, connection, msg):
hass.data[DOMAIN].async_clear_completed()
hass.bus.async_fire(EVENT, {"action": "clear"})
connection.send_message(websocket_api.result_message(msg["id"]))


@callback
def websocket_handle_reorder(hass, connection, msg):
Comment thread
balloob marked this conversation as resolved.
"""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_message(websocket_api.result_message(msg_id))
Comment thread
ShaneQi marked this conversation as resolved.
Outdated
except KeyError:
connection.send_message(
websocket_api.error_message(
msg_id, "item_not_found", "One or more item id(s) not found."
)
)
Comment thread
ShaneQi marked this conversation as resolved.
Outdated
except vol.Invalid as err:
connection.send_message(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also use send_error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this and the above 2 issues.

websocket_api.error_message(msg_id, "bad_request", f"{err}")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the websocket_api error ERR_INVALID_FORMAT

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

)
122 changes: 122 additions & 0 deletions tests/components/shopping_list/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,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"] == "item_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"] == "bad_request"