Skip to content

Commit eab1205

Browse files
authored
Add config flow title placeholder update infrastructure (home-assistant#154353)
1 parent a991dcb commit eab1205

File tree

3 files changed

+106
-5
lines changed

3 files changed

+106
-5
lines changed

homeassistant/config_entries.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2859,6 +2859,29 @@ def async_get_supported_subentry_types(
28592859
"""Return subentries supported by this handler."""
28602860
return {}
28612861

2862+
@callback
2863+
def async_update_title_placeholders(
2864+
self, title_placeholders: Mapping[str, str]
2865+
) -> None:
2866+
"""Update title placeholders for the discovery notification and notify listeners.
2867+
2868+
This updates the flow context title_placeholders and notifies listeners
2869+
(such as the frontend) to reload the flow state, updating the discovery
2870+
notification title.
2871+
2872+
Only call this method when the flow is not progressing to a new step
2873+
(e.g., from a callback that receives updated data). If the flow is
2874+
progressing to a new step, set title_placeholders directly in context
2875+
before returning the step result, as the step change will trigger
2876+
listener notification automatically.
2877+
"""
2878+
# Context is typed as TypedDict but is mutable dict at runtime
2879+
current_placeholders = cast(
2880+
dict[str, str], self.context.setdefault("title_placeholders", {})
2881+
)
2882+
current_placeholders.update(title_placeholders)
2883+
self.async_notify_flow_changed()
2884+
28622885
@callback
28632886
def _async_abort_entries_match(
28642887
self, match_dict: dict[str, Any] | None = None

homeassistant/data_entry_flow.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,7 @@ async def _async_configure(
432432
!= result.get("description_placeholders")
433433
)
434434
):
435-
# Tell frontend to reload the flow state.
436-
self.hass.bus.async_fire_internal(
437-
EVENT_DATA_ENTRY_FLOW_PROGRESSED,
438-
{"handler": flow.handler, "flow_id": flow_id, "refresh": True},
439-
)
435+
flow.async_notify_flow_changed()
440436

441437
return result
442438

@@ -886,6 +882,17 @@ def async_update_progress(self, progress: float) -> None:
886882
{"handler": self.handler, "flow_id": self.flow_id, "progress": progress},
887883
)
888884

885+
@callback
886+
def async_notify_flow_changed(self) -> None:
887+
"""Notify listeners that the flow has changed.
888+
889+
This notifies listeners (such as the frontend) to reload the flow state.
890+
"""
891+
self.hass.bus.async_fire_internal(
892+
EVENT_DATA_ENTRY_FLOW_PROGRESSED,
893+
{"handler": self.handler, "flow_id": self.flow_id, "refresh": True},
894+
)
895+
889896
@callback
890897
def async_show_progress_done(self, *, next_step_id: str) -> _FlowResultT:
891898
"""Mark the progress done."""

tests/test_config_entries.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9434,3 +9434,74 @@ async def async_step_user(self, user_input=None):
94349434
"working in Home Assistant 2026.3, please create a bug report at https:"
94359435
)
94369436
assert (log_text in caplog.text) == expected_log
9437+
9438+
9439+
async def test_async_update_title_placeholders(hass: HomeAssistant) -> None:
9440+
"""Test async_update_title_placeholders updates context and notifies listeners."""
9441+
9442+
class TestFlow(config_entries.ConfigFlow):
9443+
"""Test flow."""
9444+
9445+
VERSION = 1
9446+
9447+
async def async_step_user(self, user_input=None):
9448+
"""Test user step."""
9449+
self.context["title_placeholders"] = {"initial": "value"}
9450+
return self.async_show_form(step_id="user")
9451+
9452+
mock_integration(hass, MockModule("comp"))
9453+
mock_platform(hass, "comp.config_flow", None)
9454+
9455+
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
9456+
result = await hass.config_entries.flow.async_init(
9457+
"comp", context={"source": config_entries.SOURCE_USER}
9458+
)
9459+
assert result["type"] is FlowResultType.FORM
9460+
9461+
# Get the flow to check initial title_placeholders
9462+
flow = hass.config_entries.flow.async_get(result["flow_id"])
9463+
assert flow["context"]["title_placeholders"] == {"initial": "value"}
9464+
9465+
# Get the flow instance to call methods
9466+
flow_instance = hass.config_entries.flow._progress[result["flow_id"]]
9467+
9468+
# Capture events to verify frontend notification
9469+
events = async_capture_events(
9470+
hass, data_entry_flow.EVENT_DATA_ENTRY_FLOW_PROGRESSED
9471+
)
9472+
9473+
# Update title placeholders
9474+
flow_instance.async_update_title_placeholders({"name": "updated"})
9475+
await hass.async_block_till_done()
9476+
9477+
# Verify placeholders were updated (preserving existing values)
9478+
flow = hass.config_entries.flow.async_get(result["flow_id"])
9479+
assert flow["context"]["title_placeholders"] == {
9480+
"initial": "value",
9481+
"name": "updated",
9482+
}
9483+
9484+
# Verify frontend was notified
9485+
assert len(events) == 1
9486+
assert events[0].data == {
9487+
"handler": "comp",
9488+
"flow_id": result["flow_id"],
9489+
"refresh": True,
9490+
}
9491+
9492+
# Update again with overlapping key
9493+
flow_instance.async_update_title_placeholders(
9494+
{"initial": "new_value", "another": "key"}
9495+
)
9496+
await hass.async_block_till_done()
9497+
9498+
# Verify placeholders were updated correctly
9499+
flow = hass.config_entries.flow.async_get(result["flow_id"])
9500+
assert flow["context"]["title_placeholders"] == {
9501+
"initial": "new_value",
9502+
"name": "updated",
9503+
"another": "key",
9504+
}
9505+
9506+
# Verify frontend was notified again
9507+
assert len(events) == 2

0 commit comments

Comments
 (0)