1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Clarify behavior of ConfigEntry.async_on_state_change (#151628)

This commit is contained in:
Erik Montnemery
2025-09-03 17:21:28 +02:00
committed by GitHub
parent b9f24bbb2a
commit e67df73c4e
2 changed files with 69 additions and 1 deletions

View File

@@ -1178,7 +1178,13 @@ class ConfigEntry[_DataT = Any]:
@callback
def async_on_state_change(self, func: CALLBACK_TYPE) -> CALLBACK_TYPE:
"""Add a function to call when a config entry changes its state."""
"""Add a function to call when a config entry changes its state.
Note: async_on_unload listeners are called before the state is changed to
NOT_LOADED when unloading a config entry. This means the passed function
will not be called after a config entry has been unloaded, the last call
will be after the state is changed to UNLOAD_IN_PROGRESS.
"""
if self._on_state_change is None:
self._on_state_change = []
self._on_state_change.append(func)

View File

@@ -4784,6 +4784,68 @@ async def test_entry_state_change_calls_listener(
assert entry.state is target_state
@pytest.mark.parametrize(
("source_state", "target_state", "transition_method_name", "call_count"),
[
(
config_entries.ConfigEntryState.NOT_LOADED,
config_entries.ConfigEntryState.LOADED,
"async_setup",
2,
),
(
config_entries.ConfigEntryState.LOADED,
config_entries.ConfigEntryState.NOT_LOADED,
"async_unload",
1,
),
(
config_entries.ConfigEntryState.LOADED,
config_entries.ConfigEntryState.LOADED,
"async_reload",
1,
),
],
)
async def test_entry_state_change_wrapped_in_on_unload(
hass: HomeAssistant,
manager: config_entries.ConfigEntries,
source_state: config_entries.ConfigEntryState,
target_state: config_entries.ConfigEntryState,
transition_method_name: str,
call_count: int,
) -> None:
"""Test listeners get called on entry state changes.
This test wraps the listener in async_on_unload, the expectation is that
`async_on_unload` is called before the state changes to NOT_LOADED so the
listener is not called when the entry is unloaded.
"""
entry = MockConfigEntry(domain="comp", state=source_state)
entry.add_to_hass(hass)
mock_integration(
hass,
MockModule(
"comp",
async_setup=AsyncMock(return_value=True),
async_setup_entry=AsyncMock(return_value=True),
async_unload_entry=AsyncMock(return_value=True),
),
)
mock_platform(hass, "comp.config_flow", None)
hass.config.components.add("comp")
mock_state_change_callback = Mock()
entry.async_on_unload(entry.async_on_state_change(mock_state_change_callback))
transition_method = getattr(manager, transition_method_name)
await transition_method(entry.entry_id)
assert len(mock_state_change_callback.mock_calls) == call_count
assert entry.state is target_state
async def test_entry_state_change_listener_removed(
hass: HomeAssistant,
manager: config_entries.ConfigEntries,