Compare commits

..

13 Commits

Author SHA1 Message Date
abmantis
8a8b2a9b82 Merge branch 'dev' of github.com:home-assistant/core into todo_triggers 2026-03-18 15:59:27 +00:00
abmantis
12930a6670 Add todo triggers 2026-03-18 15:56:38 +00:00
abmantis
0709e053c0 Merge branch 'dev' of github.com:home-assistant/core into todo_triggers 2026-03-17 19:26:10 +00:00
Abílio Costa
df9cb4d35d Merge branch 'dev' into todo_listener_arg_none 2026-03-17 18:47:28 +00:00
abmantis
44dcdc53b5 Drop deprecation 2026-03-17 18:46:52 +00:00
abmantis
8f62e2334e Use copy.copy 2026-03-17 17:51:39 +00:00
Abílio Costa
692265cec3 Merge branch 'dev' into todo_listener_arg_none 2026-03-17 17:27:35 +00:00
abmantis
dd437bf822 Add tests 2026-03-17 17:27:07 +00:00
abmantis
32a5d8965c Keep old sig 2026-03-17 16:17:10 +00:00
abmantis
cbe1ec6f3e Copy items 2026-03-17 16:12:10 +00:00
abmantis
d5a6283f4f Use TodoItem in listeners 2026-03-17 15:50:16 +00:00
abmantis
ebdbe25751 Merge branch 'dev' of github.com:home-assistant/core into todo_listener_arg_none 2026-03-17 15:32:29 +00:00
abmantis
33355a1bf1 Remove unecessary optional from TODO listener argument 2026-03-17 15:01:11 +00:00
8 changed files with 714 additions and 27 deletions

View File

@@ -20,5 +20,16 @@
"update_item": {
"service": "mdi:clipboard-edit"
}
},
"triggers": {
"item_added": {
"trigger": "mdi:clipboard-plus"
},
"item_completed": {
"trigger": "mdi:clipboard-check"
},
"item_removed": {
"trigger": "mdi:clipboard-minus"
}
}
}

View File

@@ -98,5 +98,19 @@
"name": "Update item"
}
},
"title": "To-do list"
"title": "To-do list",
"triggers": {
"item_added": {
"description": "Triggers when a to-do item is added to a list.",
"name": "To-do item added"
},
"item_completed": {
"description": "Triggers when a to-do item is marked as done.",
"name": "To-do item completed"
},
"item_removed": {
"description": "Triggers when a to-do item is removed from a list.",
"name": "To-do item removed"
}
}
}

View File

@@ -0,0 +1,263 @@
"""Provides triggers for todo platform."""
import abc
import asyncio
from collections.abc import Callable
from dataclasses import dataclass
import functools
import logging
from typing import TYPE_CHECKING, cast, override
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID, CONF_TARGET
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.target import TargetEntityChangeTracker, TargetSelection
from homeassistant.helpers.trigger import Trigger, TriggerActionRunner, TriggerConfig
from homeassistant.helpers.typing import ConfigType
from . import TodoItem, TodoListEntity
from .const import DATA_COMPONENT, DOMAIN, TodoItemStatus
ITEM_TRIGGER_SCHEMA = vol.Schema(
{
vol.Required(CONF_TARGET): cv.TARGET_FIELDS,
}
)
_LOGGER = logging.getLogger(__name__)
def get_entity(hass: HomeAssistant, entity_id: str) -> TodoListEntity:
"""Get the todo entity for the provided entity_id."""
component: EntityComponent[TodoListEntity] = hass.data[DATA_COMPONENT]
if not (entity := component.get_entity(entity_id)) or not isinstance(
entity, TodoListEntity
):
raise HomeAssistantError(
f"Entity does not exist {entity_id} or is not a todo entity"
)
return entity
@dataclass(frozen=True, slots=True)
class TodoItemChangeEvent:
"""Data class for todo item change event."""
entity_id: str
items: list[TodoItem]
class ItemChangeListener(TargetEntityChangeTracker):
"""Helper class to listen to todo item changes for target entities."""
def __init__(
self,
hass: HomeAssistant,
target_selection: TargetSelection,
listener: Callable[[TodoItemChangeEvent], None],
) -> None:
"""Initialize the item change tracker."""
def entity_filter(entities: set[str]) -> set[str]:
return {
entity_id
for entity_id in entities
if split_entity_id(entity_id)[0] == DOMAIN
}
super().__init__(hass, target_selection, entity_filter)
self._listener = listener
self._pending_listener_task: asyncio.Task[None] | None = None
self._unsubscribe_listeners: list[CALLBACK_TYPE] = []
@override
@callback
def _handle_entities_update(self, tracked_entities: set[str]) -> None:
"""Restart the listeners when the list of entities of the tracked targets is updated."""
if self._pending_listener_task:
self._pending_listener_task.cancel()
self._pending_listener_task = self._hass.async_create_task(
self._start_listening(tracked_entities)
)
async def _start_listening(self, tracked_entities: set[str]) -> None:
"""Start listening for todo item changes."""
_LOGGER.debug("Tracking items for todos: %s", tracked_entities)
for unsub in self._unsubscribe_listeners:
unsub()
def _listener_wrapper(entity_id: str, items: list[TodoItem]) -> None:
self._listener(TodoItemChangeEvent(entity_id=entity_id, items=items))
self._unsubscribe_listeners = []
for entity_id in tracked_entities:
entity = get_entity(self._hass, entity_id)
unsub = entity.async_subscribe_updates(
functools.partial(_listener_wrapper, entity_id)
)
self._unsubscribe_listeners.append(unsub)
def unsubscribe(self) -> None:
"""Unsubscribe from all events."""
super()._unsubscribe()
if self._pending_listener_task:
self._pending_listener_task.cancel()
self._pending_listener_task = None
for unsub in self._unsubscribe_listeners:
unsub()
class ItemTriggerBase(Trigger, abc.ABC):
"""todo item trigger base."""
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
return cast(ConfigType, ITEM_TRIGGER_SCHEMA(config))
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize trigger."""
super().__init__(hass, config)
if TYPE_CHECKING:
assert config.target is not None
self._target = config.target
async def async_attach_runner(
self, run_action: TriggerActionRunner
) -> CALLBACK_TYPE:
"""Attach a trigger."""
target_selection = TargetSelection(self._target)
if not target_selection.has_any_target:
raise HomeAssistantError(f"No target defined in {self._target}")
listener = ItemChangeListener(
self._hass,
target_selection,
functools.partial(self.handle_item_change_event, run_action=run_action),
)
return listener.async_setup()
@callback
@abc.abstractmethod
def handle_item_change_event(
self, event: TodoItemChangeEvent, run_action: TriggerActionRunner
) -> None:
"""Handle todo item change event."""
class ItemAddedTrigger(ItemTriggerBase):
"""todo item added trigger."""
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize trigger."""
super().__init__(hass, config)
self._entity_item_ids: dict[str, set[str]] = {}
@override
@callback
def handle_item_change_event(
self, event: TodoItemChangeEvent, run_action: TriggerActionRunner
) -> None:
"""Listen for todo item changes."""
old_item_ids = self._entity_item_ids.get(event.entity_id, set())
current_item_ids = {item.uid for item in event.items if item.uid is not None}
added_item_ids = current_item_ids - old_item_ids
self._entity_item_ids[event.entity_id] = current_item_ids
if added_item_ids:
_LOGGER.debug(
"Detected added items with ids %s for entity %s",
added_item_ids,
event.entity_id,
)
payload = {
ATTR_ENTITY_ID: event.entity_id,
"item_ids": list(added_item_ids),
}
run_action(payload, description="todo item added trigger")
class ItemRemovedTrigger(ItemTriggerBase):
"""todo item removed trigger."""
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize trigger."""
super().__init__(hass, config)
self._entity_item_ids: dict[str, set[str]] = {}
@override
@callback
def handle_item_change_event(
self, event: TodoItemChangeEvent, run_action: TriggerActionRunner
) -> None:
"""Listen for todo item changes."""
old_item_ids = self._entity_item_ids.get(event.entity_id, set())
current_item_ids = {item.uid for item in event.items if item.uid is not None}
removed_item_ids = old_item_ids - current_item_ids
self._entity_item_ids[event.entity_id] = current_item_ids
if removed_item_ids:
_LOGGER.debug(
"Detected removed items with ids %s for entity %s",
removed_item_ids,
event.entity_id,
)
payload = {
ATTR_ENTITY_ID: event.entity_id,
"item_ids": list(removed_item_ids),
}
run_action(payload, description="todo item removed trigger")
class ItemCompletedTrigger(ItemTriggerBase):
"""todo item completed trigger."""
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize trigger."""
super().__init__(hass, config)
self._entity_completed_item_ids: dict[str, set[str]] = {}
@override
@callback
def handle_item_change_event(
self, event: TodoItemChangeEvent, run_action: TriggerActionRunner
) -> None:
"""Listen for todo item changes."""
old_item_ids = self._entity_completed_item_ids.get(event.entity_id, set())
current_item_ids = {
item.uid
for item in event.items
if item.uid is not None and item.status == TodoItemStatus.COMPLETED
}
new_completed_item_ids = current_item_ids - old_item_ids
self._entity_completed_item_ids[event.entity_id] = current_item_ids
if new_completed_item_ids:
_LOGGER.debug(
"Detected new completed items with ids %s for entity %s",
new_completed_item_ids,
event.entity_id,
)
payload = {
ATTR_ENTITY_ID: event.entity_id,
"item_ids": list(new_completed_item_ids),
}
run_action(payload, description="todo item completed trigger")
TRIGGERS: dict[str, type[Trigger]] = {
"item_added": ItemAddedTrigger,
"item_completed": ItemCompletedTrigger,
"item_removed": ItemRemovedTrigger,
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for todo platform."""
return TRIGGERS

View File

@@ -0,0 +1,8 @@
.trigger_common: &trigger_common
target:
entity:
domain: todo
item_added: *trigger_common
item_completed: *trigger_common
item_removed: *trigger_common

View File

@@ -165,8 +165,6 @@ ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema(
vol.Optional("supported_features"): [
vol.All(cv.ensure_list, [str], _validate_supported_features)
],
# Unit of measurement of the entity
vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.All(cv.ensure_list, [str]),
}
)
@@ -192,7 +190,6 @@ class EntityFilterSelectorConfig(TypedDict, total=False):
domain: str | list[str]
device_class: str | list[str]
supported_features: list[str]
unit_of_measurement: str | list[str]
DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema(

View File

@@ -1,5 +1,7 @@
"""Tests for the To-do integration."""
import uuid
from homeassistant.components.todo import DOMAIN, TodoItem, TodoListEntity
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.core import HomeAssistant
@@ -28,7 +30,15 @@ class MockTodoListEntity(TodoListEntity):
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
self._attr_todo_items.append(item)
self._attr_todo_items.append(
TodoItem(
summary=item.summary,
uid=item.uid or uuid.uuid4().hex,
status=item.status,
due=item.due,
description=item.description,
)
)
async def async_delete_todo_items(self, uids: list[str]) -> None:
"""Delete an item in the To-do list."""

View File

@@ -0,0 +1,406 @@
"""Tests for the todo item_added trigger."""
from typing import Any
import pytest
from homeassistant.components import automation
from homeassistant.components.todo import (
DOMAIN,
TodoItem,
TodoItemStatus,
TodoListEntityFeature,
)
from homeassistant.components.todo.const import ATTR_ITEM, ATTR_STATUS, TodoServices
from homeassistant.const import (
ATTR_AREA_ID,
ATTR_DEVICE_ID,
ATTR_ENTITY_ID,
ATTR_FLOOR_ID,
ATTR_LABEL_ID,
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TARGET,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
entity_registry as er,
floor_registry as fr,
label_registry as lr,
)
from homeassistant.setup import async_setup_component
from . import MockTodoListEntity, create_mock_platform
from tests.common import async_mock_service, mock_device_registry
TODO_ENTITY_ID1 = "todo.list_one"
TODO_ENTITY_ID2 = "todo.list_two"
@pytest.fixture(autouse=True)
async def todo_lists(
hass: HomeAssistant,
) -> tuple[MockTodoListEntity, MockTodoListEntity]:
"""Create two todo list entities via the mock platform."""
entity1 = _make_entity(TODO_ENTITY_ID1, unique_id="list_one")
entity2 = _make_entity(TODO_ENTITY_ID2, unique_id="list_two")
await create_mock_platform(hass, [entity1, entity2])
return entity1, entity2
@pytest.fixture
def target_todo_lists(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
area_registry: ar.AreaRegistry,
floor_registry: fr.FloorRegistry,
label_registry: lr.LabelRegistry,
) -> None:
"""Associate todo list entities with different targets.
Sets up the following target structure (per entity):
- floor_list_one / area_list_one: floor and area for list_one only
- floor_list_two / area_list_two: floor and area for list_two only
- label_both: label shared by both entities
- label_list_one / label_list_two: labels for one entity only
- device_list_one / device_list_two: devices for one entity only
"""
floor_list_one = floor_registry.async_create("floor_list_one")
area_list_one = area_registry.async_create(
"area_list_one", floor_id=floor_list_one.floor_id
)
floor_list_two = floor_registry.async_create("floor_list_two")
area_list_two = area_registry.async_create(
"area_list_two", floor_id=floor_list_two.floor_id
)
label_both = label_registry.async_create("label_both_lists")
label_list_one = label_registry.async_create("label_list_one")
label_list_two = label_registry.async_create("label_list_two")
device_list_one = dr.DeviceEntry(id="device_list_one")
device_list_two = dr.DeviceEntry(id="device_list_two")
mock_device_registry(
hass,
{
device_list_one.id: device_list_one,
device_list_two.id: device_list_two,
},
)
entity_registry.async_update_entity(
TODO_ENTITY_ID1,
area_id=area_list_one.id,
labels={label_both.label_id, label_list_one.label_id},
device_id=device_list_one.id,
)
entity_registry.async_update_entity(
TODO_ENTITY_ID2,
area_id=area_list_two.id,
labels={label_both.label_id, label_list_two.label_id},
device_id=device_list_two.id,
)
@pytest.fixture
def service_calls(hass: HomeAssistant) -> list[ServiceCall]:
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "item_added")
def _assert_service_calls(
service_calls: list[ServiceCall], expected_calls: list[dict[str, Any]]
) -> None:
"""Assert that the service calls match the expected calls."""
assert len(service_calls) == len(expected_calls), (
f"Expected {len(expected_calls)} calls, got {len(service_calls)}"
)
for call, expected in zip(service_calls, expected_calls, strict=True):
for key, value in expected.items():
assert call.data.get(key) == value, (
f"Expected call data[{key}] to be {value}, got {call.data.get(key)}"
)
async def _setup_automation(hass: HomeAssistant, target: dict[str, Any]) -> None:
"""Set up an automation with the todo.item_added trigger."""
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"triggers": [
{
CONF_PLATFORM: "todo.item_added",
CONF_TARGET: target,
},
{
CONF_PLATFORM: "todo.item_completed",
CONF_TARGET: target,
},
{
CONF_PLATFORM: "todo.item_removed",
CONF_TARGET: target,
},
],
"action": {
"service": "test.item_added",
"data_template": {
"platform": "{{ trigger.platform }}",
"entity_id": "{{ trigger.entity_id }}",
"item_ids": "{{ trigger.item_ids }}",
},
},
}
},
)
await hass.async_block_till_done()
def _make_entity(
entity_id: str,
items: list[TodoItem] | None = None,
unique_id: str | None = None,
) -> MockTodoListEntity:
"""Create a mock todo entity with the given items."""
entity = MockTodoListEntity(items or [])
entity.entity_id = entity_id
entity._attr_unique_id = unique_id
entity._attr_supported_features = (
TodoListEntityFeature.CREATE_TODO_ITEM
| TodoListEntityFeature.UPDATE_TODO_ITEM
| TodoListEntityFeature.DELETE_TODO_ITEM
| TodoListEntityFeature.MOVE_TODO_ITEM
)
return entity
async def _add_item(hass: HomeAssistant, entity_id: str, item: str) -> None:
"""Add an item to the entity."""
await hass.services.async_call(
DOMAIN,
TodoServices.ADD_ITEM,
{ATTR_ENTITY_ID: entity_id, ATTR_ITEM: item},
blocking=True,
)
async def _remove_item(hass: HomeAssistant, entity_id: str, item: str) -> None:
await hass.services.async_call(
DOMAIN,
TodoServices.REMOVE_ITEM,
{ATTR_ENTITY_ID: entity_id, ATTR_ITEM: [item]},
blocking=True,
)
async def _complete_item(hass: HomeAssistant, entity_id: str, item: str) -> None:
await hass.services.async_call(
DOMAIN,
TodoServices.UPDATE_ITEM,
{
ATTR_ENTITY_ID: entity_id,
ATTR_ITEM: item,
ATTR_STATUS: TodoItemStatus.COMPLETED,
},
blocking=True,
)
@pytest.mark.usefixtures("enable_labs_preview_features")
async def test_item_change_triggers(
hass: HomeAssistant, service_calls: list[ServiceCall]
) -> None:
"""Test item change triggers fire."""
entity = _make_entity(TODO_ENTITY_ID1)
await create_mock_platform(hass, [entity])
await _setup_automation(hass, {CONF_ENTITY_ID: TODO_ENTITY_ID1})
item1 = "item_id"
await _add_item(hass, TODO_ENTITY_ID1, item1)
await _add_item(hass, TODO_ENTITY_ID1, "other_item")
_assert_service_calls(
service_calls,
[
{"platform": "todo.item_added", "entity_id": TODO_ENTITY_ID1},
{"platform": "todo.item_added", "entity_id": TODO_ENTITY_ID1},
],
)
assert len(service_calls[0].data["item_ids"]) == 1
assert len(service_calls[1].data["item_ids"]) == 1
item1_id = service_calls[0].data["item_ids"][0]
item2_id = service_calls[1].data["item_ids"][0]
assert item1_id != item2_id
service_calls.clear()
await _complete_item(hass, TODO_ENTITY_ID1, item1)
_assert_service_calls(
service_calls,
[
{
"platform": "todo.item_completed",
"entity_id": TODO_ENTITY_ID1,
"item_ids": [item1_id],
},
],
)
service_calls.clear()
await _remove_item(hass, TODO_ENTITY_ID1, item1)
_assert_service_calls(
service_calls,
[
{
"platform": "todo.item_removed",
"entity_id": TODO_ENTITY_ID1,
"item_ids": [item1_id],
},
],
)
@pytest.mark.usefixtures("enable_labs_preview_features", "target_todo_lists")
@pytest.mark.parametrize(
"included_target",
[
{CONF_ENTITY_ID: TODO_ENTITY_ID1},
{ATTR_AREA_ID: "area_list_one"},
{ATTR_FLOOR_ID: "floor_list_one"},
{ATTR_LABEL_ID: "label_list_one"},
{ATTR_DEVICE_ID: "device_list_one"},
],
)
async def test_item_change_trigger_does_not_fire_for_other_entity(
hass: HomeAssistant,
service_calls: list[ServiceCall],
included_target: dict[str, Any],
) -> None:
"""Test item_added trigger only fires for the targeted entity."""
included_entity = TODO_ENTITY_ID1
excluded_entity = TODO_ENTITY_ID2
await _setup_automation(hass, included_target)
# Add item to excluded entity (not targeted)
await _add_item(hass, excluded_entity, "Untargeted item")
_assert_service_calls(service_calls, [])
# Add item to included entity (targeted)
await _add_item(hass, included_entity, "Targeted item")
_assert_service_calls(
service_calls,
[{"platform": "todo.item_added", "entity_id": included_entity}],
)
targeted_item_id = service_calls[0].data["item_ids"][0]
service_calls.clear()
# Complete item on excluded entity (not targeted) - should not fire
await _complete_item(hass, excluded_entity, "Untargeted item")
_assert_service_calls(service_calls, [])
# Complete item on included entity (targeted) - should fire
await _complete_item(hass, included_entity, targeted_item_id)
_assert_service_calls(
service_calls,
[
{
"platform": "todo.item_completed",
"entity_id": included_entity,
"item_ids": [targeted_item_id],
}
],
)
service_calls.clear()
# Remove item on excluded entity (not targeted) - should not fire
await _remove_item(hass, excluded_entity, "Untargeted item")
_assert_service_calls(service_calls, [])
# Remove item on included entity (targeted) - should fire
await _remove_item(hass, included_entity, targeted_item_id)
_assert_service_calls(
service_calls,
[
{
"platform": "todo.item_removed",
"entity_id": included_entity,
"item_ids": [targeted_item_id],
}
],
)
@pytest.mark.usefixtures("enable_labs_preview_features", "target_todo_lists")
@pytest.mark.parametrize(
"trigger_target",
[
{CONF_ENTITY_ID: [TODO_ENTITY_ID1, TODO_ENTITY_ID2]},
{ATTR_AREA_ID: ["area_list_one", "area_list_two"]},
{ATTR_FLOOR_ID: ["floor_list_one", "floor_list_two"]},
{ATTR_LABEL_ID: "label_both_lists"},
{ATTR_DEVICE_ID: ["device_list_one", "device_list_two"]},
],
ids=["entity_id", "area", "floor", "label", "device"],
)
async def test_item_change_trigger_with_multiple_target_entities(
hass: HomeAssistant,
service_calls: list[ServiceCall],
trigger_target: dict[str, Any],
) -> None:
"""Test item_added trigger fires for multiple targeted entities."""
await _setup_automation(hass, target=trigger_target)
await _add_item(hass, TODO_ENTITY_ID1, "Item on list one")
await _add_item(hass, TODO_ENTITY_ID2, "Item on list two")
_assert_service_calls(
service_calls,
[
{"platform": "todo.item_added", "entity_id": TODO_ENTITY_ID1},
{"platform": "todo.item_added", "entity_id": TODO_ENTITY_ID2},
],
)
item_one_id = service_calls[0].data["item_ids"][0]
item_two_id = service_calls[1].data["item_ids"][0]
service_calls.clear()
await _complete_item(hass, TODO_ENTITY_ID1, item_one_id)
await _complete_item(hass, TODO_ENTITY_ID2, item_two_id)
_assert_service_calls(
service_calls,
[
{
"platform": "todo.item_completed",
"entity_id": TODO_ENTITY_ID1,
"item_ids": [item_one_id],
},
{
"platform": "todo.item_completed",
"entity_id": TODO_ENTITY_ID2,
"item_ids": [item_two_id],
},
],
)
service_calls.clear()
await _remove_item(hass, TODO_ENTITY_ID1, item_one_id)
await _remove_item(hass, TODO_ENTITY_ID2, item_two_id)
_assert_service_calls(
service_calls,
[
{
"platform": "todo.item_removed",
"entity_id": TODO_ENTITY_ID1,
"item_ids": [item_one_id],
},
{
"platform": "todo.item_removed",
"entity_id": TODO_ENTITY_ID2,
"item_ids": [item_two_id],
},
],
)

View File

@@ -297,24 +297,6 @@ def test_device_selector_schema_error(schema) -> None:
("light.abc123", "blah.blah", FAKE_UUID),
(None,),
),
(
{
"filter": [
{"unit_of_measurement": "baguette"},
]
},
("light.abc123", "blah.blah", FAKE_UUID),
(None,),
),
(
{
"filter": [
{"unit_of_measurement": ["currywurst", "bratwurst"]},
]
},
("light.abc123", "blah.blah", FAKE_UUID),
(None,),
),
],
)
def test_entity_selector_schema(schema, valid_selections, invalid_selections) -> None:
@@ -337,10 +319,6 @@ def test_entity_selector_schema(schema, valid_selections, invalid_selections) ->
{"filter": [{"supported_features": ["light.LightEntityFeature.blah"]}]},
# supported_features should be used under the filter key
{"supported_features": ["light.LightEntityFeature.EFFECT"]},
# unit_of_measurement should be used under the filter key
{"unit_of_measurement": ["currywurst", "bratwurst"]},
# Invalid unit_of_measurement
{"filter": [{"unit_of_measurement": 42}]},
# reorder can only be used when multiple is true
{"reorder": True},
{"reorder": True, "multiple": False},