mirror of
https://github.com/home-assistant/core.git
synced 2026-09-12 19:49:41 +01:00
Avoid duplicates when using HassShoppingListAddItem to modify shopping list (#179726)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot App
Copilot Autofix powered by AI
parent
bb2e7f1629
commit
76bf1328f9
@@ -1,12 +1,12 @@
|
||||
"""Intents for the Shopping List integration."""
|
||||
|
||||
from typing import override
|
||||
from typing import cast, override
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, intent
|
||||
|
||||
from .common import NoMatchingShoppingListItem, _get_shopping_data
|
||||
from .const import DOMAIN, EVENT_SHOPPING_LIST_UPDATED
|
||||
from .const import DOMAIN
|
||||
|
||||
INTENT_ADD_ITEM = "HassShoppingListAddItem"
|
||||
INTENT_COMPLETE_ITEM = "HassShoppingListCompleteItem"
|
||||
@@ -32,12 +32,28 @@ class AddItemIntent(intent.IntentHandler):
|
||||
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
||||
"""Handle the intent."""
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
item = slots["item"]["value"].strip()
|
||||
await _get_shopping_data(intent_obj.hass).async_add(item)
|
||||
item_name = slots["item"]["value"].strip()
|
||||
shopping_data = _get_shopping_data(intent_obj.hass)
|
||||
completed_match = None
|
||||
normalized_name = item_name.casefold()
|
||||
for item in shopping_data.items:
|
||||
name = item["name"]
|
||||
if not isinstance(name, str) or name.casefold() != normalized_name:
|
||||
continue
|
||||
if not item["complete"]:
|
||||
return intent_obj.create_response()
|
||||
if completed_match is None:
|
||||
completed_match = item
|
||||
|
||||
response = intent_obj.create_response()
|
||||
intent_obj.hass.bus.async_fire(EVENT_SHOPPING_LIST_UPDATED)
|
||||
return response
|
||||
if completed_match is None:
|
||||
await shopping_data.async_add(item_name)
|
||||
else:
|
||||
await shopping_data.async_update(
|
||||
cast(str, completed_match["id"]),
|
||||
{"name": cast(str, completed_match["name"]), "complete": False},
|
||||
)
|
||||
|
||||
return intent_obj.create_response()
|
||||
|
||||
|
||||
class CompleteItemIntent(intent.IntentHandler):
|
||||
@@ -61,8 +77,6 @@ class CompleteItemIntent(intent.IntentHandler):
|
||||
except NoMatchingShoppingListItem:
|
||||
complete_items = []
|
||||
|
||||
intent_obj.hass.bus.async_fire(EVENT_SHOPPING_LIST_UPDATED)
|
||||
|
||||
response = intent_obj.create_response()
|
||||
response.async_set_speech_slots({"completed_items": complete_items})
|
||||
|
||||
|
||||
@@ -51,12 +51,15 @@ async def test_add_item(
|
||||
hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion
|
||||
) -> None:
|
||||
"""Test adding an item intent."""
|
||||
events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": " beer "}}
|
||||
)
|
||||
assert len(_get_shopping_data(hass).items) == 1
|
||||
assert _get_shopping_data(hass).items[0]["name"] == "beer" # name was trimmed
|
||||
assert len(events) == 1
|
||||
assert events[0].data["action"] == "add"
|
||||
|
||||
# Response text is now handled by default conversation agent
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
@@ -797,6 +800,21 @@ async def test_add_item_service(
|
||||
assert_shopping_list_data(hass, snapshot)
|
||||
|
||||
|
||||
async def test_add_item_service_allows_duplicates(
|
||||
hass: HomeAssistant, sl_setup: None
|
||||
) -> None:
|
||||
"""Test adding duplicate items with the shopping list service."""
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_ITEM,
|
||||
{ATTR_NAME: "beer"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(_get_shopping_data(hass).items) == 2
|
||||
|
||||
|
||||
async def test_remove_item_service(
|
||||
hass: HomeAssistant, sl_setup: None, snapshot: SnapshotAssertion
|
||||
) -> None:
|
||||
|
||||
@@ -1,24 +1,70 @@
|
||||
"""Test Shopping List intents."""
|
||||
|
||||
from homeassistant.components.shopping_list.common import _get_shopping_data
|
||||
from homeassistant.components.shopping_list.const import EVENT_SHOPPING_LIST_UPDATED
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from tests.common import async_capture_events
|
||||
|
||||
async def test_complete_item_intent(hass: HomeAssistant, sl_setup) -> None:
|
||||
|
||||
async def test_add_item_intent_reactivates_first_completed_match(
|
||||
hass: HomeAssistant, sl_setup: None
|
||||
) -> None:
|
||||
"""Test reactivating the first completed matching item."""
|
||||
shopping_data = _get_shopping_data(hass)
|
||||
first_item = await shopping_data.async_add("Beer", complete=True)
|
||||
second_item = await shopping_data.async_add("BEER", complete=True)
|
||||
first_item_id = first_item["id"]
|
||||
second_item_id = second_item["id"]
|
||||
events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert len(shopping_data.items) == 2
|
||||
assert shopping_data.items[0]["id"] == first_item_id
|
||||
assert shopping_data.items[0]["name"] == "Beer"
|
||||
assert shopping_data.items[0]["complete"] is False
|
||||
assert shopping_data.items[1]["id"] == second_item_id
|
||||
assert shopping_data.items[1]["complete"] is True
|
||||
assert len(events) == 1
|
||||
assert events[0].data["action"] == "update"
|
||||
|
||||
|
||||
async def test_add_item_intent_keeps_existing_active_match(
|
||||
hass: HomeAssistant, sl_setup: None
|
||||
) -> None:
|
||||
"""Test keeping an existing active matching item."""
|
||||
shopping_data = _get_shopping_data(hass)
|
||||
completed_item = await shopping_data.async_add("Beer", complete=True)
|
||||
active_item = await shopping_data.async_add("BEER")
|
||||
completed_item_id = completed_item["id"]
|
||||
active_item_id = active_item["id"]
|
||||
events = async_capture_events(hass, EVENT_SHOPPING_LIST_UPDATED)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert len(shopping_data.items) == 2
|
||||
assert shopping_data.items[0]["id"] == completed_item_id
|
||||
assert shopping_data.items[0]["complete"] is True
|
||||
assert shopping_data.items[1]["id"] == active_item_id
|
||||
assert shopping_data.items[1]["complete"] is False
|
||||
assert not events
|
||||
|
||||
|
||||
async def test_complete_item_intent(hass: HomeAssistant, sl_setup: None) -> None:
|
||||
"""Test complete item."""
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "soda"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
shopping_data = _get_shopping_data(hass)
|
||||
await shopping_data.async_add("soda")
|
||||
await shopping_data.async_add("beer")
|
||||
await shopping_data.async_add("beer")
|
||||
await shopping_data.async_add("wine")
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassShoppingListCompleteItem", {"item": {"value": "beer"}}
|
||||
@@ -28,8 +74,8 @@ async def test_complete_item_intent(hass: HomeAssistant, sl_setup) -> None:
|
||||
completed_items = response.speech_slots.get("completed_items")
|
||||
assert len(completed_items) == 2
|
||||
assert completed_items[0]["name"] == "beer"
|
||||
assert _get_shopping_data(hass).items[1]["complete"]
|
||||
assert _get_shopping_data(hass).items[2]["complete"]
|
||||
assert shopping_data.items[1]["complete"]
|
||||
assert shopping_data.items[2]["complete"]
|
||||
|
||||
# Complete again
|
||||
response = await intent.async_handle(
|
||||
@@ -38,8 +84,8 @@ async def test_complete_item_intent(hass: HomeAssistant, sl_setup) -> None:
|
||||
|
||||
assert response.response_type is intent.IntentResponseType.ACTION_DONE
|
||||
assert response.speech_slots.get("completed_items") == []
|
||||
assert _get_shopping_data(hass).items[1]["complete"]
|
||||
assert _get_shopping_data(hass).items[2]["complete"]
|
||||
assert shopping_data.items[1]["complete"]
|
||||
assert shopping_data.items[2]["complete"]
|
||||
|
||||
|
||||
async def test_complete_item_intent_not_found(hass: HomeAssistant, sl_setup) -> None:
|
||||
|
||||
Reference in New Issue
Block a user