mirror of
https://github.com/home-assistant/core.git
synced 2026-09-24 15:31:52 -05:00
Fix todo item triggers after config entry reload (#181255)
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com> Co-authored-by: abmantis <amfcalt@gmail.com>
This commit is contained in:
co-authored by
Cursor
Abílio Costa
abmantis
parent
1d9ae4b184
commit
5059007bfe
@@ -10,11 +10,24 @@ from typing import TYPE_CHECKING, cast, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_OPTIONS, CONF_TARGET
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback, split_entity_id
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_OPTIONS,
|
||||
CONF_TARGET,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
Event,
|
||||
EventStateChangedData,
|
||||
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.event import async_track_state_change_event
|
||||
from homeassistant.helpers.target import TargetEntityChangeTracker, TargetSelection
|
||||
from homeassistant.helpers.trigger import (
|
||||
Trigger,
|
||||
@@ -79,6 +92,7 @@ class ItemChangeListener(TargetEntityChangeTracker):
|
||||
|
||||
self._pending_listener_task: asyncio.Task[None] | None = None
|
||||
self._unsubscribe_listeners: list[CALLBACK_TYPE] = []
|
||||
self._state_change_unsub: CALLBACK_TYPE | None = None
|
||||
|
||||
@override
|
||||
@callback
|
||||
@@ -114,6 +128,31 @@ class ItemChangeListener(TargetEntityChangeTracker):
|
||||
)
|
||||
self._unsubscribe_listeners.append(unsub)
|
||||
|
||||
if self._state_change_unsub:
|
||||
self._state_change_unsub()
|
||||
self._state_change_unsub = None
|
||||
if tracked_entities:
|
||||
self._state_change_unsub = async_track_state_change_event(
|
||||
self._hass, tracked_entities, self._async_todo_entity_state_changed
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_todo_entity_state_changed(
|
||||
self, event: Event[EventStateChangedData]
|
||||
) -> None:
|
||||
"""Handle entities becoming available.
|
||||
|
||||
This is required so that when the config entry is reloaded,
|
||||
we start listening on the newly created classes.
|
||||
"""
|
||||
new_state = event.data["new_state"]
|
||||
old_state = event.data["old_state"]
|
||||
if new_state is None or new_state.state == STATE_UNAVAILABLE:
|
||||
return
|
||||
if old_state is not None and old_state.state != STATE_UNAVAILABLE:
|
||||
return
|
||||
self._handle_entities_update(self._referenced_entities())
|
||||
|
||||
@override
|
||||
@callback
|
||||
def _unsubscribe(self) -> None:
|
||||
@@ -122,6 +161,9 @@ class ItemChangeListener(TargetEntityChangeTracker):
|
||||
if self._pending_listener_task:
|
||||
self._pending_listener_task.cancel()
|
||||
self._pending_listener_task = None
|
||||
if self._state_change_unsub:
|
||||
self._state_change_unsub()
|
||||
self._state_change_unsub = None
|
||||
for unsub in self._unsubscribe_listeners:
|
||||
unsub()
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from homeassistant.components.todo import (
|
||||
TodoListEntityFeature,
|
||||
)
|
||||
from homeassistant.components.todo.const import ATTR_ITEM, ATTR_STATUS, TodoServices
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_AREA_ID,
|
||||
ATTR_DEVICE_ID,
|
||||
@@ -31,11 +32,17 @@ from homeassistant.helpers import (
|
||||
floor_registry as fr,
|
||||
label_registry as lr,
|
||||
)
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import MockTodoListEntity, create_mock_platform
|
||||
from . import TEST_DOMAIN, MockTodoListEntity, create_mock_platform
|
||||
|
||||
from tests.common import async_mock_service, mock_device_registry
|
||||
from tests.common import (
|
||||
MockPlatform,
|
||||
async_mock_service,
|
||||
mock_device_registry,
|
||||
mock_platform,
|
||||
)
|
||||
from tests.components.common import assert_trigger_options_supported
|
||||
|
||||
TODO_ENTITY_ID1 = "todo.list_one"
|
||||
@@ -474,6 +481,83 @@ async def test_new_entity_added_to_target_fires_triggers(
|
||||
)
|
||||
|
||||
|
||||
async def test_item_change_triggers_after_config_entry_reload(
|
||||
hass: HomeAssistant,
|
||||
service_calls: list[ServiceCall],
|
||||
) -> None:
|
||||
"""Test triggers still fire after the todo list's config entry is reloaded.
|
||||
|
||||
Reload recreates the entity object without changing the registry entry, so
|
||||
the trigger must re-subscribe rather than keep the old object.
|
||||
"""
|
||||
await _setup_automation(hass, {CONF_ENTITY_ID: TODO_ENTITY_ID1})
|
||||
|
||||
config_entry = hass.config_entries.async_entries(TEST_DOMAIN)[0]
|
||||
|
||||
async def async_setup_entry_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
async_add_entities(
|
||||
[
|
||||
_make_entity(
|
||||
TODO_ENTITY_ID1,
|
||||
unique_id="list_one",
|
||||
items=[
|
||||
TodoItem(
|
||||
summary="existing_item",
|
||||
uid="existing_id",
|
||||
status=TodoItemStatus.NEEDS_ACTION,
|
||||
)
|
||||
],
|
||||
),
|
||||
_make_entity(TODO_ENTITY_ID2, unique_id="list_two"),
|
||||
]
|
||||
)
|
||||
|
||||
mock_platform(
|
||||
hass,
|
||||
f"{TEST_DOMAIN}.{DOMAIN}",
|
||||
MockPlatform(async_setup_entry=async_setup_entry_platform),
|
||||
)
|
||||
assert await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await _add_item(hass, TODO_ENTITY_ID1, "item_after_reload")
|
||||
_assert_service_calls(
|
||||
service_calls,
|
||||
[{"platform": "todo.item_added", "entity_id": TODO_ENTITY_ID1}],
|
||||
)
|
||||
item_id = service_calls[0].data["item_ids"][0]
|
||||
service_calls.clear()
|
||||
|
||||
await _complete_item(hass, TODO_ENTITY_ID1, item_id)
|
||||
_assert_service_calls(
|
||||
service_calls,
|
||||
[
|
||||
{
|
||||
"platform": "todo.item_completed",
|
||||
"entity_id": TODO_ENTITY_ID1,
|
||||
"item_ids": [item_id],
|
||||
}
|
||||
],
|
||||
)
|
||||
service_calls.clear()
|
||||
|
||||
await _remove_item(hass, TODO_ENTITY_ID1, item_id)
|
||||
_assert_service_calls(
|
||||
service_calls,
|
||||
[
|
||||
{
|
||||
"platform": "todo.item_removed",
|
||||
"entity_id": TODO_ENTITY_ID1,
|
||||
"item_ids": [item_id],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def test_trigger_skips_missing_entity(
|
||||
hass: HomeAssistant,
|
||||
service_calls: list[ServiceCall],
|
||||
|
||||
Reference in New Issue
Block a user