Add HassListAddItem intent (#103716)

* Add HassListAddItem intent

* Add missing list test
This commit is contained in:
Michael Hansen 2023-11-14 12:00:30 -06:00 committed by GitHub
parent 2cb4435cf0
commit be8507f870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 0 deletions

View File

@ -0,0 +1,54 @@
"""Intents for the todo integration."""
from __future__ import annotations
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from . import DOMAIN, TodoItem, TodoListEntity
INTENT_LIST_ADD_ITEM = "HassListAddItem"
async def async_setup_intents(hass: HomeAssistant) -> None:
"""Set up the todo intents."""
intent.async_register(hass, ListAddItemIntent())
class ListAddItemIntent(intent.IntentHandler):
"""Handle ListAddItem intents."""
intent_type = INTENT_LIST_ADD_ITEM
slot_schema = {"item": cv.string, "name": cv.string}
async def async_handle(self, intent_obj: intent.Intent):
"""Handle the intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
item = slots["item"]["value"]
list_name = slots["name"]["value"]
component: EntityComponent[TodoListEntity] = hass.data[DOMAIN]
target_list: TodoListEntity | None = None
# Find matching list
for list_state in intent.async_match_states(
hass, name=list_name, domains=[DOMAIN]
):
target_list = component.get_entity(list_state.entity_id)
if target_list is not None:
break
if target_list is None:
raise intent.IntentHandleError(f"No to-do list: {list_name}")
assert target_list is not None
# Add to list
await target_list.async_create_todo_item(TodoItem(item))
response = intent_obj.create_response()
response.response_type = intent.IntentResponseType.ACTION_DONE
return response

View File

@ -13,11 +13,13 @@ from homeassistant.components.todo import (
TodoItemStatus, TodoItemStatus,
TodoListEntity, TodoListEntity,
TodoListEntityFeature, TodoListEntityFeature,
intent as todo_intent,
) )
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from tests.common import ( from tests.common import (
@ -37,6 +39,18 @@ class MockFlow(ConfigFlow):
"""Test flow.""" """Test flow."""
class MockTodoListEntity(TodoListEntity):
"""Test todo list entity."""
def __init__(self) -> None:
"""Initialize entity."""
self.items: list[TodoItem] = []
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
self.items.append(item)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]: def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
"""Mock config flow.""" """Mock config flow."""
@ -737,3 +751,70 @@ async def test_move_item_unsupported(
resp = await client.receive_json() resp = await client.receive_json()
assert resp.get("id") == 1 assert resp.get("id") == 1
assert resp.get("error", {}).get("code") == "not_supported" assert resp.get("error", {}).get("code") == "not_supported"
async def test_add_item_intent(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test adding items to lists using an intent."""
await todo_intent.async_setup_intents(hass)
entity1 = MockTodoListEntity()
entity1._attr_name = "List 1"
entity1.entity_id = "todo.list_1"
entity2 = MockTodoListEntity()
entity2._attr_name = "List 2"
entity2.entity_id = "todo.list_2"
await create_mock_platform(hass, [entity1, entity2])
# Add to first list
response = await intent.async_handle(
hass,
"test",
todo_intent.INTENT_LIST_ADD_ITEM,
{"item": {"value": "beer"}, "name": {"value": "list 1"}},
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert len(entity1.items) == 1
assert len(entity2.items) == 0
assert entity1.items[0].summary == "beer"
entity1.items.clear()
# Add to second list
response = await intent.async_handle(
hass,
"test",
todo_intent.INTENT_LIST_ADD_ITEM,
{"item": {"value": "cheese"}, "name": {"value": "List 2"}},
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert len(entity1.items) == 0
assert len(entity2.items) == 1
assert entity2.items[0].summary == "cheese"
# List name is case insensitive
response = await intent.async_handle(
hass,
"test",
todo_intent.INTENT_LIST_ADD_ITEM,
{"item": {"value": "wine"}, "name": {"value": "lIST 2"}},
)
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert len(entity1.items) == 0
assert len(entity2.items) == 2
assert entity2.items[1].summary == "wine"
# Missing list
with pytest.raises(intent.IntentHandleError):
await intent.async_handle(
hass,
"test",
todo_intent.INTENT_LIST_ADD_ITEM,
{"item": {"value": "wine"}, "name": {"value": "This list does not exist"}},
)