Compare commits
4 commits
dev
...
synesthesi
Author | SHA1 | Date | |
---|---|---|---|
|
a1c23b0235 | ||
|
4ba9358d5c | ||
|
800f816e9f | ||
|
9b604b4a1c |
6 changed files with 149 additions and 25 deletions
|
@ -1,38 +1,20 @@
|
|||
"""Intents for the Shopping List integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import DOMAIN, EVENT_SHOPPING_LIST_UPDATED
|
||||
from . import DOMAIN
|
||||
|
||||
INTENT_ADD_ITEM = "HassShoppingListAddItem"
|
||||
INTENT_LAST_ITEMS = "HassShoppingListLastItems"
|
||||
|
||||
|
||||
async def async_setup_intents(hass):
|
||||
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
"""Set up the Shopping List intents."""
|
||||
intent.async_register(hass, AddItemIntent())
|
||||
intent.async_register(hass, ListTopItemsIntent())
|
||||
|
||||
|
||||
class AddItemIntent(intent.IntentHandler):
|
||||
"""Handle AddItem intents."""
|
||||
|
||||
intent_type = INTENT_ADD_ITEM
|
||||
slot_schema = {"item": cv.string}
|
||||
|
||||
async def async_handle(self, intent_obj: intent.Intent):
|
||||
"""Handle the intent."""
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
item = slots["item"]["value"]
|
||||
await intent_obj.hass.data[DOMAIN].async_add(item)
|
||||
|
||||
response = intent_obj.create_response()
|
||||
intent_obj.hass.bus.async_fire(EVENT_SHOPPING_LIST_UPDATED)
|
||||
return response
|
||||
|
||||
|
||||
class ListTopItemsIntent(intent.IntentHandler):
|
||||
"""Handle AddItem intents."""
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Shopping List",
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"dependencies": ["http"],
|
||||
"dependencies": ["http", "intent", "todo"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/shopping_list",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal"
|
||||
|
|
73
homeassistant/components/todo/intent.py
Normal file
73
homeassistant/components/todo/intent.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
"""Intents for the todo integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.shopping_list import EVENT_SHOPPING_LIST_UPDATED
|
||||
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_ADD_ITEM = "HassShoppingListAddItem"
|
||||
|
||||
|
||||
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
"""Set up the todo intents."""
|
||||
intent.async_register(hass, AddItemIntent())
|
||||
|
||||
|
||||
class AddItemIntent(intent.IntentHandler):
|
||||
"""Handle AddItem intents."""
|
||||
|
||||
intent_type = INTENT_ADD_ITEM
|
||||
slot_schema = {"item": cv.string, vol.Optional("list"): 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"]
|
||||
|
||||
component: EntityComponent[TodoListEntity] = hass.data[DOMAIN]
|
||||
list_entities: list[TodoListEntity] = list(component.entities)
|
||||
if not list_entities:
|
||||
raise intent.IntentHandleError("No list entities")
|
||||
|
||||
target_list: TodoListEntity | None = None
|
||||
|
||||
if "list" in slots:
|
||||
# Add to a list by name
|
||||
list_name = slots["list"]["value"]
|
||||
|
||||
# Find matching list
|
||||
matching_states = intent.async_match_states(
|
||||
hass, name=list_name, domains=[DOMAIN]
|
||||
)
|
||||
for list_state in matching_states:
|
||||
for maybe_list in list_entities:
|
||||
if maybe_list.entity_id == list_state.entity_id:
|
||||
target_list = maybe_list
|
||||
break
|
||||
|
||||
if target_list is not None:
|
||||
break
|
||||
|
||||
if target_list is None:
|
||||
raise intent.IntentHandleError(f"No list named {list_name}")
|
||||
else:
|
||||
# Add to the first list
|
||||
target_list = list_entities[0]
|
||||
|
||||
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
|
||||
intent_obj.hass.bus.async_fire(EVENT_SHOPPING_LIST_UPDATED)
|
||||
return response
|
|
@ -21,13 +21,12 @@ def mock_shopping_list_io():
|
|||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Config Entry fixture."""
|
||||
return MockConfigEntry(domain="shopping_list")
|
||||
return MockConfigEntry(domain="shopping_list", entry_id="1234")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def sl_setup(hass: HomeAssistant, mock_config_entry: MockConfigEntry):
|
||||
"""Set up the shopping list."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
|
|
@ -11,7 +11,8 @@ from homeassistant.exceptions import HomeAssistantError
|
|||
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
TEST_ENTITY = "todo.shopping_list"
|
||||
# NOTE: This depends on the config entry_id in sl_setup
|
||||
TEST_ENTITY = "todo.shopping_list_1234"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -13,11 +13,13 @@ from homeassistant.components.todo import (
|
|||
TodoItemStatus,
|
||||
TodoListEntity,
|
||||
TodoListEntityFeature,
|
||||
intent as todo_intent,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from tests.common import (
|
||||
|
@ -37,6 +39,18 @@ class MockFlow(ConfigFlow):
|
|||
"""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)
|
||||
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
|
||||
"""Mock config flow."""
|
||||
|
@ -728,3 +742,58 @@ async def test_move_item_unsupported(
|
|||
resp = await client.receive_json()
|
||||
assert resp.get("id") == 1
|
||||
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_ADD_ITEM, {"item": {"value": "beer"}}
|
||||
)
|
||||
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 list by name
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
todo_intent.INTENT_ADD_ITEM,
|
||||
{"item": {"value": "cheese"}, "list": {"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_ADD_ITEM,
|
||||
{"item": {"value": "wine"}, "list": {"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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue