Add config flow title placeholder update infrastructure (#154353)

This commit is contained in:
J. Nick Koston
2025-10-13 11:15:28 -04:00
committed by GitHub
parent a991dcbe6a
commit eab1205823
3 changed files with 106 additions and 5 deletions
+23
View File
@@ -2859,6 +2859,29 @@ class ConfigFlow(ConfigEntryBaseFlow):
"""Return subentries supported by this handler."""
return {}
@callback
def async_update_title_placeholders(
self, title_placeholders: Mapping[str, str]
) -> None:
"""Update title placeholders for the discovery notification and notify listeners.
This updates the flow context title_placeholders and notifies listeners
(such as the frontend) to reload the flow state, updating the discovery
notification title.
Only call this method when the flow is not progressing to a new step
(e.g., from a callback that receives updated data). If the flow is
progressing to a new step, set title_placeholders directly in context
before returning the step result, as the step change will trigger
listener notification automatically.
"""
# Context is typed as TypedDict but is mutable dict at runtime
current_placeholders = cast(
dict[str, str], self.context.setdefault("title_placeholders", {})
)
current_placeholders.update(title_placeholders)
self.async_notify_flow_changed()
@callback
def _async_abort_entries_match(
self, match_dict: dict[str, Any] | None = None
+12 -5
View File
@@ -432,11 +432,7 @@ class FlowManager(abc.ABC, Generic[_FlowContextT, _FlowResultT, _HandlerT]):
!= result.get("description_placeholders")
)
):
# Tell frontend to reload the flow state.
self.hass.bus.async_fire_internal(
EVENT_DATA_ENTRY_FLOW_PROGRESSED,
{"handler": flow.handler, "flow_id": flow_id, "refresh": True},
)
flow.async_notify_flow_changed()
return result
@@ -886,6 +882,17 @@ class FlowHandler(Generic[_FlowContextT, _FlowResultT, _HandlerT]):
{"handler": self.handler, "flow_id": self.flow_id, "progress": progress},
)
@callback
def async_notify_flow_changed(self) -> None:
"""Notify listeners that the flow has changed.
This notifies listeners (such as the frontend) to reload the flow state.
"""
self.hass.bus.async_fire_internal(
EVENT_DATA_ENTRY_FLOW_PROGRESSED,
{"handler": self.handler, "flow_id": self.flow_id, "refresh": True},
)
@callback
def async_show_progress_done(self, *, next_step_id: str) -> _FlowResultT:
"""Mark the progress done."""
+71
View File
@@ -9434,3 +9434,74 @@ async def test_create_entry_existing_unique_id(
"working in Home Assistant 2026.3, please create a bug report at https:"
)
assert (log_text in caplog.text) == expected_log
async def test_async_update_title_placeholders(hass: HomeAssistant) -> None:
"""Test async_update_title_placeholders updates context and notifies listeners."""
class TestFlow(config_entries.ConfigFlow):
"""Test flow."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Test user step."""
self.context["title_placeholders"] = {"initial": "value"}
return self.async_show_form(step_id="user")
mock_integration(hass, MockModule("comp"))
mock_platform(hass, "comp.config_flow", None)
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
result = await hass.config_entries.flow.async_init(
"comp", context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
# Get the flow to check initial title_placeholders
flow = hass.config_entries.flow.async_get(result["flow_id"])
assert flow["context"]["title_placeholders"] == {"initial": "value"}
# Get the flow instance to call methods
flow_instance = hass.config_entries.flow._progress[result["flow_id"]]
# Capture events to verify frontend notification
events = async_capture_events(
hass, data_entry_flow.EVENT_DATA_ENTRY_FLOW_PROGRESSED
)
# Update title placeholders
flow_instance.async_update_title_placeholders({"name": "updated"})
await hass.async_block_till_done()
# Verify placeholders were updated (preserving existing values)
flow = hass.config_entries.flow.async_get(result["flow_id"])
assert flow["context"]["title_placeholders"] == {
"initial": "value",
"name": "updated",
}
# Verify frontend was notified
assert len(events) == 1
assert events[0].data == {
"handler": "comp",
"flow_id": result["flow_id"],
"refresh": True,
}
# Update again with overlapping key
flow_instance.async_update_title_placeholders(
{"initial": "new_value", "another": "key"}
)
await hass.async_block_till_done()
# Verify placeholders were updated correctly
flow = hass.config_entries.flow.async_get(result["flow_id"])
assert flow["context"]["title_placeholders"] == {
"initial": "new_value",
"name": "updated",
"another": "key",
}
# Verify frontend was notified again
assert len(events) == 2