mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add preview to sensor group config and option flows (#83638)
This commit is contained in:
parent
52b1e34af0
commit
b885dfa5a8
@ -7,8 +7,10 @@ from typing import Any, cast
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
from homeassistant.const import CONF_ENTITIES, CONF_TYPE
|
from homeassistant.const import CONF_ENTITIES, CONF_TYPE
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er, selector
|
from homeassistant.helpers import entity_registry as er, selector
|
||||||
from homeassistant.helpers.schema_config_entry_flow import (
|
from homeassistant.helpers.schema_config_entry_flow import (
|
||||||
SchemaCommonFlowHandler,
|
SchemaCommonFlowHandler,
|
||||||
@ -22,6 +24,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
|||||||
from . import DOMAIN
|
from . import DOMAIN
|
||||||
from .binary_sensor import CONF_ALL
|
from .binary_sensor import CONF_ALL
|
||||||
from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC
|
from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC
|
||||||
|
from .sensor import SensorGroup
|
||||||
|
|
||||||
_STATISTIC_MEASURES = [
|
_STATISTIC_MEASURES = [
|
||||||
"min",
|
"min",
|
||||||
@ -36,15 +39,22 @@ _STATISTIC_MEASURES = [
|
|||||||
|
|
||||||
|
|
||||||
async def basic_group_options_schema(
|
async def basic_group_options_schema(
|
||||||
domain: str | list[str], handler: SchemaCommonFlowHandler
|
domain: str | list[str], handler: SchemaCommonFlowHandler | None
|
||||||
) -> vol.Schema:
|
) -> vol.Schema:
|
||||||
"""Generate options schema."""
|
"""Generate options schema."""
|
||||||
|
if handler is None:
|
||||||
|
entity_selector = selector.selector(
|
||||||
|
{"entity": {"domain": domain, "multiple": True}}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
entity_selector = entity_selector_without_own_entities(
|
||||||
|
cast(SchemaOptionsFlowHandler, handler.parent_handler),
|
||||||
|
selector.EntitySelectorConfig(domain=domain, multiple=True),
|
||||||
|
)
|
||||||
|
|
||||||
return vol.Schema(
|
return vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITIES): entity_selector_without_own_entities(
|
vol.Required(CONF_ENTITIES): entity_selector,
|
||||||
cast(SchemaOptionsFlowHandler, handler.parent_handler),
|
|
||||||
selector.EntitySelectorConfig(domain=domain, multiple=True),
|
|
||||||
),
|
|
||||||
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
|
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -96,7 +106,7 @@ SENSOR_OPTIONS = {
|
|||||||
|
|
||||||
|
|
||||||
async def sensor_options_schema(
|
async def sensor_options_schema(
|
||||||
domain: str, handler: SchemaCommonFlowHandler
|
domain: str, handler: SchemaCommonFlowHandler | None
|
||||||
) -> vol.Schema:
|
) -> vol.Schema:
|
||||||
"""Generate options schema."""
|
"""Generate options schema."""
|
||||||
return (
|
return (
|
||||||
@ -184,6 +194,7 @@ CONFIG_FLOW = {
|
|||||||
"sensor": SchemaFlowFormStep(
|
"sensor": SchemaFlowFormStep(
|
||||||
SENSOR_CONFIG_SCHEMA,
|
SENSOR_CONFIG_SCHEMA,
|
||||||
validate_user_input=set_group_type("sensor"),
|
validate_user_input=set_group_type("sensor"),
|
||||||
|
preview="group_sensor",
|
||||||
),
|
),
|
||||||
"switch": SchemaFlowFormStep(
|
"switch": SchemaFlowFormStep(
|
||||||
basic_group_config_schema("switch"),
|
basic_group_config_schema("switch"),
|
||||||
@ -202,7 +213,10 @@ OPTIONS_FLOW = {
|
|||||||
"media_player": SchemaFlowFormStep(
|
"media_player": SchemaFlowFormStep(
|
||||||
partial(basic_group_options_schema, "media_player")
|
partial(basic_group_options_schema, "media_player")
|
||||||
),
|
),
|
||||||
"sensor": SchemaFlowFormStep(partial(sensor_options_schema, "sensor")),
|
"sensor": SchemaFlowFormStep(
|
||||||
|
partial(sensor_options_schema, "sensor"),
|
||||||
|
preview="group_sensor",
|
||||||
|
),
|
||||||
"switch": SchemaFlowFormStep(partial(light_switch_options_schema, "switch")),
|
"switch": SchemaFlowFormStep(partial(light_switch_options_schema, "switch")),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,6 +255,12 @@ class GroupConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
_async_hide_members(hass, options[CONF_ENTITIES], hidden_by)
|
_async_hide_members(hass, options[CONF_ENTITIES], hidden_by)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
@staticmethod
|
||||||
|
def async_setup_preview(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up preview WS API."""
|
||||||
|
websocket_api.async_register_command(hass, ws_preview_sensor)
|
||||||
|
|
||||||
|
|
||||||
def _async_hide_members(
|
def _async_hide_members(
|
||||||
hass: HomeAssistant, members: list[str], hidden_by: er.RegistryEntryHider | None
|
hass: HomeAssistant, members: list[str], hidden_by: er.RegistryEntryHider | None
|
||||||
@ -253,3 +273,56 @@ def _async_hide_members(
|
|||||||
if entity_id not in registry.entities:
|
if entity_id not in registry.entities:
|
||||||
continue
|
continue
|
||||||
registry.async_update_entity(entity_id, hidden_by=hidden_by)
|
registry.async_update_entity(entity_id, hidden_by=hidden_by)
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required("type"): "group/sensor/start_preview",
|
||||||
|
vol.Required("flow_id"): str,
|
||||||
|
vol.Required("flow_type"): vol.Any("config_flow", "options_flow"),
|
||||||
|
vol.Required("user_input"): dict,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def ws_preview_sensor(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Generate a preview."""
|
||||||
|
if msg["flow_type"] == "config_flow":
|
||||||
|
validated = SENSOR_CONFIG_SCHEMA(msg["user_input"])
|
||||||
|
ignore_non_numeric = False
|
||||||
|
name = validated["name"]
|
||||||
|
else:
|
||||||
|
validated = (await sensor_options_schema("sensor", None))(msg["user_input"])
|
||||||
|
flow_status = hass.config_entries.options.async_get(msg["flow_id"])
|
||||||
|
config_entry = hass.config_entries.async_get_entry(flow_status["handler"])
|
||||||
|
if not config_entry:
|
||||||
|
raise HomeAssistantError
|
||||||
|
ignore_non_numeric = validated[CONF_IGNORE_NON_NUMERIC]
|
||||||
|
name = config_entry.options["name"]
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_preview_updated(state: str, attributes: Mapping[str, Any]) -> None:
|
||||||
|
"""Forward config entry state events to websocket."""
|
||||||
|
connection.send_message(
|
||||||
|
websocket_api.event_message(
|
||||||
|
msg["id"], {"state": state, "attributes": attributes}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
sensor = SensorGroup(
|
||||||
|
None,
|
||||||
|
name,
|
||||||
|
validated[CONF_ENTITIES],
|
||||||
|
ignore_non_numeric,
|
||||||
|
validated[CONF_TYPE],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
sensor.hass = hass
|
||||||
|
|
||||||
|
connection.send_result(msg["id"])
|
||||||
|
connection.subscriptions[msg["id"]] = sensor.async_start_preview(
|
||||||
|
async_preview_updated
|
||||||
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Platform allowing several sensors to be grouped into one sensor to provide numeric combinations."""
|
"""Platform allowing several sensors to be grouped into one sensor to provide numeric combinations."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable, Mapping
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
import statistics
|
import statistics
|
||||||
@ -33,7 +33,7 @@ from homeassistant.const import (
|
|||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, State, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, State, callback
|
||||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
@ -303,6 +303,26 @@ class SensorGroup(GroupEntity, SensorEntity):
|
|||||||
self._state_incorrect: set[str] = set()
|
self._state_incorrect: set[str] = set()
|
||||||
self._extra_state_attribute: dict[str, Any] = {}
|
self._extra_state_attribute: dict[str, Any] = {}
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_start_preview(
|
||||||
|
self,
|
||||||
|
preview_callback: Callable[[str, Mapping[str, Any]], None],
|
||||||
|
) -> CALLBACK_TYPE:
|
||||||
|
"""Render a preview."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_state_changed_listener(
|
||||||
|
event: EventType[EventStateChangedData] | None,
|
||||||
|
) -> None:
|
||||||
|
"""Handle child updates."""
|
||||||
|
self.async_update_group_state()
|
||||||
|
preview_callback(*self._async_generate_attributes())
|
||||||
|
|
||||||
|
async_state_changed_listener(None)
|
||||||
|
return async_track_state_change_event(
|
||||||
|
self.hass, self._entity_ids, async_state_changed_listener
|
||||||
|
)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
|
|
||||||
|
@ -1814,6 +1814,14 @@ class ConfigFlow(data_entry_flow.FlowHandler):
|
|||||||
class OptionsFlowManager(data_entry_flow.FlowManager):
|
class OptionsFlowManager(data_entry_flow.FlowManager):
|
||||||
"""Flow to set options for a configuration entry."""
|
"""Flow to set options for a configuration entry."""
|
||||||
|
|
||||||
|
def _async_get_config_entry(self, config_entry_id: str) -> ConfigEntry:
|
||||||
|
"""Return config entry or raise if not found."""
|
||||||
|
entry = self.hass.config_entries.async_get_entry(config_entry_id)
|
||||||
|
if entry is None:
|
||||||
|
raise UnknownEntry(config_entry_id)
|
||||||
|
|
||||||
|
return entry
|
||||||
|
|
||||||
async def async_create_flow(
|
async def async_create_flow(
|
||||||
self,
|
self,
|
||||||
handler_key: str,
|
handler_key: str,
|
||||||
@ -1825,10 +1833,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager):
|
|||||||
|
|
||||||
Entry_id and flow.handler is the same thing to map entry with flow.
|
Entry_id and flow.handler is the same thing to map entry with flow.
|
||||||
"""
|
"""
|
||||||
entry = self.hass.config_entries.async_get_entry(handler_key)
|
entry = self._async_get_config_entry(handler_key)
|
||||||
if entry is None:
|
|
||||||
raise UnknownEntry(handler_key)
|
|
||||||
|
|
||||||
handler = await _async_get_flow_handler(self.hass, entry.domain, {})
|
handler = await _async_get_flow_handler(self.hass, entry.domain, {})
|
||||||
return handler.async_get_options_flow(entry)
|
return handler.async_get_options_flow(entry)
|
||||||
|
|
||||||
@ -1853,6 +1858,14 @@ class OptionsFlowManager(data_entry_flow.FlowManager):
|
|||||||
result["result"] = True
|
result["result"] = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def _async_setup_preview(self, flow: data_entry_flow.FlowHandler) -> None:
|
||||||
|
"""Set up preview for an option flow handler."""
|
||||||
|
entry = self._async_get_config_entry(flow.handler)
|
||||||
|
await _load_integration(self.hass, entry.domain, {})
|
||||||
|
if entry.domain not in self._preview:
|
||||||
|
self._preview.add(entry.domain)
|
||||||
|
flow.async_setup_preview(self.hass)
|
||||||
|
|
||||||
|
|
||||||
class OptionsFlow(data_entry_flow.FlowHandler):
|
class OptionsFlow(data_entry_flow.FlowHandler):
|
||||||
"""Base class for config options flows."""
|
"""Base class for config options flows."""
|
||||||
@ -2016,15 +2029,9 @@ async def support_remove_from_device(hass: HomeAssistant, domain: str) -> bool:
|
|||||||
return hasattr(component, "async_remove_config_entry_device")
|
return hasattr(component, "async_remove_config_entry_device")
|
||||||
|
|
||||||
|
|
||||||
async def _async_get_flow_handler(
|
async def _load_integration(
|
||||||
hass: HomeAssistant, domain: str, hass_config: ConfigType
|
hass: HomeAssistant, domain: str, hass_config: ConfigType
|
||||||
) -> type[ConfigFlow]:
|
) -> None:
|
||||||
"""Get a flow handler for specified domain."""
|
|
||||||
|
|
||||||
# First check if there is a handler registered for the domain
|
|
||||||
if domain in hass.config.components and (handler := HANDLERS.get(domain)):
|
|
||||||
return handler
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
integration = await loader.async_get_integration(hass, domain)
|
integration = await loader.async_get_integration(hass, domain)
|
||||||
except loader.IntegrationNotFound as err:
|
except loader.IntegrationNotFound as err:
|
||||||
@ -2044,6 +2051,18 @@ async def _async_get_flow_handler(
|
|||||||
)
|
)
|
||||||
raise data_entry_flow.UnknownHandler
|
raise data_entry_flow.UnknownHandler
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_get_flow_handler(
|
||||||
|
hass: HomeAssistant, domain: str, hass_config: ConfigType
|
||||||
|
) -> type[ConfigFlow]:
|
||||||
|
"""Get a flow handler for specified domain."""
|
||||||
|
|
||||||
|
# First check if there is a handler registered for the domain
|
||||||
|
if domain in hass.config.components and (handler := HANDLERS.get(domain)):
|
||||||
|
return handler
|
||||||
|
|
||||||
|
await _load_integration(hass, domain, hass_config)
|
||||||
|
|
||||||
if handler := HANDLERS.get(domain):
|
if handler := HANDLERS.get(domain):
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ class FlowResult(TypedDict, total=False):
|
|||||||
last_step: bool | None
|
last_step: bool | None
|
||||||
menu_options: list[str] | dict[str, str]
|
menu_options: list[str] | dict[str, str]
|
||||||
options: Mapping[str, Any]
|
options: Mapping[str, Any]
|
||||||
|
preview: str | None
|
||||||
progress_action: str
|
progress_action: str
|
||||||
reason: str
|
reason: str
|
||||||
required: bool
|
required: bool
|
||||||
@ -135,6 +136,7 @@ class FlowManager(abc.ABC):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the flow manager."""
|
"""Initialize the flow manager."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
self._preview: set[str] = set()
|
||||||
self._progress: dict[str, FlowHandler] = {}
|
self._progress: dict[str, FlowHandler] = {}
|
||||||
self._handler_progress_index: dict[str, set[str]] = {}
|
self._handler_progress_index: dict[str, set[str]] = {}
|
||||||
self._init_data_process_index: dict[type, set[str]] = {}
|
self._init_data_process_index: dict[type, set[str]] = {}
|
||||||
@ -395,6 +397,10 @@ class FlowManager(abc.ABC):
|
|||||||
flow.flow_id, flow.handler, err.reason, err.description_placeholders
|
flow.flow_id, flow.handler, err.reason, err.description_placeholders
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Setup the flow handler's preview if needed
|
||||||
|
if result.get("preview") is not None:
|
||||||
|
await self._async_setup_preview(flow)
|
||||||
|
|
||||||
if not isinstance(result["type"], FlowResultType):
|
if not isinstance(result["type"], FlowResultType):
|
||||||
result["type"] = FlowResultType(result["type"]) # type: ignore[unreachable]
|
result["type"] = FlowResultType(result["type"]) # type: ignore[unreachable]
|
||||||
report(
|
report(
|
||||||
@ -429,6 +435,12 @@ class FlowManager(abc.ABC):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def _async_setup_preview(self, flow: FlowHandler) -> None:
|
||||||
|
"""Set up preview for a flow handler."""
|
||||||
|
if flow.handler not in self._preview:
|
||||||
|
self._preview.add(flow.handler)
|
||||||
|
flow.async_setup_preview(self.hass)
|
||||||
|
|
||||||
|
|
||||||
class FlowHandler:
|
class FlowHandler:
|
||||||
"""Handle a data entry flow."""
|
"""Handle a data entry flow."""
|
||||||
@ -504,6 +516,7 @@ class FlowHandler:
|
|||||||
errors: dict[str, str] | None = None,
|
errors: dict[str, str] | None = None,
|
||||||
description_placeholders: Mapping[str, str | None] | None = None,
|
description_placeholders: Mapping[str, str | None] | None = None,
|
||||||
last_step: bool | None = None,
|
last_step: bool | None = None,
|
||||||
|
preview: str | None = None,
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Return the definition of a form to gather user input."""
|
"""Return the definition of a form to gather user input."""
|
||||||
return FlowResult(
|
return FlowResult(
|
||||||
@ -515,6 +528,7 @@ class FlowHandler:
|
|||||||
errors=errors,
|
errors=errors,
|
||||||
description_placeholders=description_placeholders,
|
description_placeholders=description_placeholders,
|
||||||
last_step=last_step, # Display next or submit button in frontend
|
last_step=last_step, # Display next or submit button in frontend
|
||||||
|
preview=preview, # Display preview component in frontend
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -635,6 +649,11 @@ class FlowHandler:
|
|||||||
def async_remove(self) -> None:
|
def async_remove(self) -> None:
|
||||||
"""Notification that the flow has been removed."""
|
"""Notification that the flow has been removed."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
@staticmethod
|
||||||
|
def async_setup_preview(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up preview."""
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _create_abort_data(
|
def _create_abort_data(
|
||||||
|
@ -756,31 +756,10 @@ class Entity(ABC):
|
|||||||
return f"{device_name} {name}" if device_name else name
|
return f"{device_name} {name}" if device_name else name
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_write_ha_state(self) -> None:
|
def _async_generate_attributes(self) -> tuple[str, dict[str, Any]]:
|
||||||
"""Write the state to the state machine."""
|
"""Calculate state string and attribute mapping."""
|
||||||
if self._platform_state == EntityPlatformState.REMOVED:
|
|
||||||
# Polling returned after the entity has already been removed
|
|
||||||
return
|
|
||||||
|
|
||||||
hass = self.hass
|
|
||||||
entity_id = self.entity_id
|
|
||||||
entry = self.registry_entry
|
entry = self.registry_entry
|
||||||
|
|
||||||
if entry and entry.disabled_by:
|
|
||||||
if not self._disabled_reported:
|
|
||||||
self._disabled_reported = True
|
|
||||||
_LOGGER.warning(
|
|
||||||
(
|
|
||||||
"Entity %s is incorrectly being triggered for updates while it"
|
|
||||||
" is disabled. This is a bug in the %s integration"
|
|
||||||
),
|
|
||||||
entity_id,
|
|
||||||
self.platform.platform_name,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
start = timer()
|
|
||||||
|
|
||||||
attr = self.capability_attributes
|
attr = self.capability_attributes
|
||||||
attr = dict(attr) if attr else {}
|
attr = dict(attr) if attr else {}
|
||||||
|
|
||||||
@ -818,6 +797,33 @@ class Entity(ABC):
|
|||||||
if (supported_features := self.supported_features) is not None:
|
if (supported_features := self.supported_features) is not None:
|
||||||
attr[ATTR_SUPPORTED_FEATURES] = supported_features
|
attr[ATTR_SUPPORTED_FEATURES] = supported_features
|
||||||
|
|
||||||
|
return (state, attr)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_write_ha_state(self) -> None:
|
||||||
|
"""Write the state to the state machine."""
|
||||||
|
if self._platform_state == EntityPlatformState.REMOVED:
|
||||||
|
# Polling returned after the entity has already been removed
|
||||||
|
return
|
||||||
|
|
||||||
|
hass = self.hass
|
||||||
|
entity_id = self.entity_id
|
||||||
|
|
||||||
|
if (entry := self.registry_entry) and entry.disabled_by:
|
||||||
|
if not self._disabled_reported:
|
||||||
|
self._disabled_reported = True
|
||||||
|
_LOGGER.warning(
|
||||||
|
(
|
||||||
|
"Entity %s is incorrectly being triggered for updates while it"
|
||||||
|
" is disabled. This is a bug in the %s integration"
|
||||||
|
),
|
||||||
|
entity_id,
|
||||||
|
self.platform.platform_name,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
start = timer()
|
||||||
|
state, attr = self._async_generate_attributes()
|
||||||
end = timer()
|
end = timer()
|
||||||
|
|
||||||
if end - start > 0.4 and not self._slow_reported:
|
if end - start > 0.4 and not self._slow_reported:
|
||||||
|
@ -78,6 +78,9 @@ class SchemaFlowFormStep(SchemaFlowStep):
|
|||||||
have priority over the suggested values.
|
have priority over the suggested values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
preview: str | None = None
|
||||||
|
"""Optional preview component."""
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class SchemaFlowMenuStep(SchemaFlowStep):
|
class SchemaFlowMenuStep(SchemaFlowStep):
|
||||||
@ -237,6 +240,7 @@ class SchemaCommonFlowHandler:
|
|||||||
data_schema=data_schema,
|
data_schema=data_schema,
|
||||||
errors=errors,
|
errors=errors,
|
||||||
last_step=last_step,
|
last_step=last_step,
|
||||||
|
preview=form_step.preview,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_menu_step(
|
async def _async_menu_step(
|
||||||
@ -271,7 +275,10 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC):
|
|||||||
raise UnknownHandler
|
raise UnknownHandler
|
||||||
|
|
||||||
return SchemaOptionsFlowHandler(
|
return SchemaOptionsFlowHandler(
|
||||||
config_entry, cls.options_flow, cls.async_options_flow_finished
|
config_entry,
|
||||||
|
cls.options_flow,
|
||||||
|
cls.async_options_flow_finished,
|
||||||
|
cls.async_setup_preview,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an async_get_options_flow method
|
# Create an async_get_options_flow method
|
||||||
@ -285,6 +292,11 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC):
|
|||||||
"""Initialize config flow."""
|
"""Initialize config flow."""
|
||||||
self._common_handler = SchemaCommonFlowHandler(self, self.config_flow, None)
|
self._common_handler = SchemaCommonFlowHandler(self, self.config_flow, None)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
@staticmethod
|
||||||
|
def async_setup_preview(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up preview."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@callback
|
@callback
|
||||||
def async_supports_options_flow(
|
def async_supports_options_flow(
|
||||||
@ -357,6 +369,7 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry):
|
|||||||
options_flow: Mapping[str, SchemaFlowStep],
|
options_flow: Mapping[str, SchemaFlowStep],
|
||||||
async_options_flow_finished: Callable[[HomeAssistant, Mapping[str, Any]], None]
|
async_options_flow_finished: Callable[[HomeAssistant, Mapping[str, Any]], None]
|
||||||
| None = None,
|
| None = None,
|
||||||
|
async_setup_preview: Callable[[HomeAssistant], None] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize options flow.
|
"""Initialize options flow.
|
||||||
|
|
||||||
@ -378,6 +391,9 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry):
|
|||||||
types.MethodType(self._async_step(step), self),
|
types.MethodType(self._async_step(step), self),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if async_setup_preview:
|
||||||
|
setattr(self, "async_setup_preview", async_setup_preview)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _async_step(step_id: str) -> Callable:
|
def _async_step(step_id: str) -> Callable:
|
||||||
"""Generate a step handler."""
|
"""Generate a step handler."""
|
||||||
|
@ -123,6 +123,7 @@ async def test_legacy_subscription_repair_flow(
|
|||||||
"errors": None,
|
"errors": None,
|
||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
@ -205,6 +206,7 @@ async def test_legacy_subscription_repair_flow_timeout(
|
|||||||
"errors": None,
|
"errors": None,
|
||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch("homeassistant.components.cloud.repairs.MAX_RETRIES", new=0):
|
with patch("homeassistant.components.cloud.repairs.MAX_RETRIES", new=0):
|
||||||
|
@ -396,6 +396,7 @@ async def test_initialize_flow(hass: HomeAssistant, client) -> None:
|
|||||||
},
|
},
|
||||||
"errors": {"username": "Should be unique."},
|
"errors": {"username": "Should be unique."},
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -571,6 +572,7 @@ async def test_two_step_flow(
|
|||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"errors": None,
|
"errors": None,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||||
@ -647,6 +649,7 @@ async def test_continue_flow_unauth(
|
|||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"errors": None,
|
"errors": None,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
hass_admin_user.groups = []
|
hass_admin_user.groups = []
|
||||||
@ -822,6 +825,7 @@ async def test_options_flow(hass: HomeAssistant, client) -> None:
|
|||||||
"description_placeholders": {"enabled": "Set to true to be true"},
|
"description_placeholders": {"enabled": "Set to true to be true"},
|
||||||
"errors": None,
|
"errors": None,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -917,6 +921,7 @@ async def test_two_step_options_flow(hass: HomeAssistant, client) -> None:
|
|||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"errors": None,
|
"errors": None,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||||
@ -998,6 +1003,7 @@ async def test_options_flow_with_invalid_data(hass: HomeAssistant, client) -> No
|
|||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"errors": None,
|
"errors": None,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch.dict(HANDLERS, {"test": TestFlow}):
|
with patch.dict(HANDLERS, {"test": TestFlow}):
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
'flow_id': <ANY>,
|
'flow_id': <ANY>,
|
||||||
'handler': 'gardena_bluetooth',
|
'handler': 'gardena_bluetooth',
|
||||||
'last_step': None,
|
'last_step': None,
|
||||||
|
'preview': None,
|
||||||
'step_id': 'confirm',
|
'step_id': 'confirm',
|
||||||
'type': <FlowResultType.FORM: 'form'>,
|
'type': <FlowResultType.FORM: 'form'>,
|
||||||
})
|
})
|
||||||
@ -136,6 +137,7 @@
|
|||||||
'flow_id': <ANY>,
|
'flow_id': <ANY>,
|
||||||
'handler': 'gardena_bluetooth',
|
'handler': 'gardena_bluetooth',
|
||||||
'last_step': None,
|
'last_step': None,
|
||||||
|
'preview': None,
|
||||||
'step_id': 'user',
|
'step_id': 'user',
|
||||||
'type': <FlowResultType.FORM: 'form'>,
|
'type': <FlowResultType.FORM: 'form'>,
|
||||||
})
|
})
|
||||||
@ -150,6 +152,7 @@
|
|||||||
'flow_id': <ANY>,
|
'flow_id': <ANY>,
|
||||||
'handler': 'gardena_bluetooth',
|
'handler': 'gardena_bluetooth',
|
||||||
'last_step': None,
|
'last_step': None,
|
||||||
|
'preview': None,
|
||||||
'step_id': 'confirm',
|
'step_id': 'confirm',
|
||||||
'type': <FlowResultType.FORM: 'form'>,
|
'type': <FlowResultType.FORM: 'form'>,
|
||||||
})
|
})
|
||||||
@ -198,6 +201,7 @@
|
|||||||
'flow_id': <ANY>,
|
'flow_id': <ANY>,
|
||||||
'handler': 'gardena_bluetooth',
|
'handler': 'gardena_bluetooth',
|
||||||
'last_step': None,
|
'last_step': None,
|
||||||
|
'preview': None,
|
||||||
'step_id': 'user',
|
'step_id': 'user',
|
||||||
'type': <FlowResultType.FORM: 'form'>,
|
'type': <FlowResultType.FORM: 'form'>,
|
||||||
})
|
})
|
||||||
@ -212,6 +216,7 @@
|
|||||||
'flow_id': <ANY>,
|
'flow_id': <ANY>,
|
||||||
'handler': 'gardena_bluetooth',
|
'handler': 'gardena_bluetooth',
|
||||||
'last_step': None,
|
'last_step': None,
|
||||||
|
'preview': None,
|
||||||
'step_id': 'confirm',
|
'step_id': 'confirm',
|
||||||
'type': <FlowResultType.FORM: 'form'>,
|
'type': <FlowResultType.FORM: 'form'>,
|
||||||
})
|
})
|
||||||
|
@ -10,6 +10,7 @@ from homeassistant.data_entry_flow import FlowResultType
|
|||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
from tests.typing import WebSocketGenerator
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -446,3 +447,174 @@ async def test_options_flow_hides_members(
|
|||||||
|
|
||||||
assert registry.async_get(f"{group_type}.one").hidden_by == hidden_by
|
assert registry.async_get(f"{group_type}.one").hidden_by == hidden_by
|
||||||
assert registry.async_get(f"{group_type}.three").hidden_by == hidden_by
|
assert registry.async_get(f"{group_type}.three").hidden_by == hidden_by
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_sensor_preview(
|
||||||
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test the config flow preview."""
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
input_sensors = ["sensor.input_one", "sensor.input_two"]
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == FlowResultType.MENU
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{"next_step_id": "sensor"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "sensor"
|
||||||
|
assert result["errors"] is None
|
||||||
|
assert result["preview"] == "group_sensor"
|
||||||
|
|
||||||
|
await client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "group/sensor/start_preview",
|
||||||
|
"flow_id": result["flow_id"],
|
||||||
|
"flow_type": "config_flow",
|
||||||
|
"user_input": {
|
||||||
|
"name": "My sensor group",
|
||||||
|
"entities": input_sensors,
|
||||||
|
"type": "max",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert msg["result"] is None
|
||||||
|
|
||||||
|
msg = await client.receive_json()
|
||||||
|
assert msg["event"] == {
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "My sensor group",
|
||||||
|
"icon": "mdi:calculator",
|
||||||
|
},
|
||||||
|
"state": "unavailable",
|
||||||
|
}
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.input_one", "10")
|
||||||
|
hass.states.async_set("sensor.input_two", "20")
|
||||||
|
|
||||||
|
msg = await client.receive_json()
|
||||||
|
assert msg["event"] == {
|
||||||
|
"attributes": {
|
||||||
|
"entity_id": ["sensor.input_one", "sensor.input_two"],
|
||||||
|
"friendly_name": "My sensor group",
|
||||||
|
"icon": "mdi:calculator",
|
||||||
|
"max_entity_id": "sensor.input_two",
|
||||||
|
},
|
||||||
|
"state": "20.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_option_flow_sensor_preview(
|
||||||
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test the option flow preview."""
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={
|
||||||
|
"entities": ["sensor.input_one", "sensor.input_two"],
|
||||||
|
"group_type": "sensor",
|
||||||
|
"hide_members": False,
|
||||||
|
"name": "My sensor group",
|
||||||
|
"type": "min",
|
||||||
|
},
|
||||||
|
title="My min_max",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
input_sensors = ["sensor.input_one", "sensor.input_two"]
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["errors"] is None
|
||||||
|
assert result["preview"] == "group_sensor"
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.input_one", "10")
|
||||||
|
hass.states.async_set("sensor.input_two", "20")
|
||||||
|
|
||||||
|
await client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "group/sensor/start_preview",
|
||||||
|
"flow_id": result["flow_id"],
|
||||||
|
"flow_type": "options_flow",
|
||||||
|
"user_input": {
|
||||||
|
"entities": input_sensors,
|
||||||
|
"type": "min",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert msg["result"] is None
|
||||||
|
|
||||||
|
msg = await client.receive_json()
|
||||||
|
assert msg["event"] == {
|
||||||
|
"attributes": {
|
||||||
|
"entity_id": ["sensor.input_one", "sensor.input_two"],
|
||||||
|
"friendly_name": "My sensor group",
|
||||||
|
"icon": "mdi:calculator",
|
||||||
|
"min_entity_id": "sensor.input_one",
|
||||||
|
},
|
||||||
|
"state": "10.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_option_flow_sensor_preview_config_entry_removed(
|
||||||
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test the option flow preview where the config entry is removed."""
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={
|
||||||
|
"entities": ["sensor.input_one", "sensor.input_two"],
|
||||||
|
"group_type": "sensor",
|
||||||
|
"hide_members": False,
|
||||||
|
"name": "My sensor group",
|
||||||
|
"type": "min",
|
||||||
|
},
|
||||||
|
title="My min_max",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
input_sensors = ["sensor.input_one", "sensor.input_two"]
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["errors"] is None
|
||||||
|
assert result["preview"] == "group_sensor"
|
||||||
|
|
||||||
|
await hass.config_entries.async_remove(config_entry.entry_id)
|
||||||
|
|
||||||
|
await client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "group/sensor/start_preview",
|
||||||
|
"flow_id": result["flow_id"],
|
||||||
|
"flow_type": "options_flow",
|
||||||
|
"user_input": {
|
||||||
|
"entities": input_sensors,
|
||||||
|
"type": "min",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await client.receive_json()
|
||||||
|
assert not msg["success"]
|
||||||
|
assert msg["error"] == {"code": "unknown_error", "message": "Unknown error"}
|
||||||
|
@ -84,6 +84,7 @@ async def test_supervisor_issue_repair_flow(
|
|||||||
"errors": None,
|
"errors": None,
|
||||||
"description_placeholders": {"reference": "/dev/sda1"},
|
"description_placeholders": {"reference": "/dev/sda1"},
|
||||||
"last_step": True,
|
"last_step": True,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
@ -292,6 +293,7 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions_and_confir
|
|||||||
"errors": None,
|
"errors": None,
|
||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"last_step": True,
|
"last_step": True,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
@ -371,6 +373,7 @@ async def test_supervisor_issue_repair_flow_skip_confirmation(
|
|||||||
"errors": None,
|
"errors": None,
|
||||||
"description_placeholders": None,
|
"description_placeholders": None,
|
||||||
"last_step": True,
|
"last_step": True,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
@ -580,6 +583,7 @@ async def test_supervisor_issue_docker_config_repair_flow(
|
|||||||
"errors": None,
|
"errors": None,
|
||||||
"description_placeholders": {"components": "Home Assistant\n- test"},
|
"description_placeholders": {"components": "Home Assistant\n- test"},
|
||||||
"last_step": True,
|
"last_step": True,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
|
@ -212,6 +212,7 @@ async def test_issues_created(
|
|||||||
"flow_id": ANY,
|
"flow_id": ANY,
|
||||||
"handler": DOMAIN,
|
"handler": DOMAIN,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
"step_id": "confirm",
|
"step_id": "confirm",
|
||||||
"type": "form",
|
"type": "form",
|
||||||
}
|
}
|
||||||
|
@ -313,6 +313,7 @@ async def test_fix_issue(
|
|||||||
"flow_id": ANY,
|
"flow_id": ANY,
|
||||||
"handler": domain,
|
"handler": domain,
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
"step_id": step,
|
"step_id": step,
|
||||||
"type": "form",
|
"type": "form",
|
||||||
}
|
}
|
||||||
|
@ -264,6 +264,7 @@ async def test_pin_form_init(pin_form) -> None:
|
|||||||
"step_id": "pin",
|
"step_id": "pin",
|
||||||
"type": "form",
|
"type": "form",
|
||||||
"last_step": None,
|
"last_step": None,
|
||||||
|
"preview": None,
|
||||||
}
|
}
|
||||||
assert pin_form == expected
|
assert pin_form == expected
|
||||||
|
|
||||||
|
@ -1178,6 +1178,27 @@ async def test_entry_options_abort(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_entry_options_unknown_config_entry(
|
||||||
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||||
|
) -> None:
|
||||||
|
"""Test that we can abort options flow."""
|
||||||
|
mock_integration(hass, MockModule("test"))
|
||||||
|
mock_entity_platform(hass, "config_flow.test", None)
|
||||||
|
|
||||||
|
class TestFlow:
|
||||||
|
"""Test flow."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Test options flow."""
|
||||||
|
|
||||||
|
with pytest.raises(config_entries.UnknownEntry):
|
||||||
|
await manager.options.async_create_flow(
|
||||||
|
"blah", context={"source": "test"}, data=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_entry_setup_succeed(
|
async def test_entry_setup_succeed(
|
||||||
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -3919,3 +3940,75 @@ async def test_task_tracking(hass: HomeAssistant) -> None:
|
|||||||
hass.loop.call_soon(event.set)
|
hass.loop.call_soon(event.set)
|
||||||
await entry._async_process_on_unload(hass)
|
await entry._async_process_on_unload(hass)
|
||||||
assert results == ["on_unload", "background", "normal"]
|
assert results == ["on_unload", "background", "normal"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_preview_supported(
|
||||||
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||||
|
) -> None:
|
||||||
|
"""Test preview support."""
|
||||||
|
|
||||||
|
preview_calls = []
|
||||||
|
|
||||||
|
class MockFlowHandler(config_entries.ConfigFlow):
|
||||||
|
"""Define a mock flow handler."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_test1(self, data):
|
||||||
|
"""Mock Reauth."""
|
||||||
|
return self.async_show_form(step_id="next")
|
||||||
|
|
||||||
|
async def async_step_test2(self, data):
|
||||||
|
"""Mock Reauth."""
|
||||||
|
return self.async_show_form(step_id="next", preview="test")
|
||||||
|
|
||||||
|
@callback
|
||||||
|
@staticmethod
|
||||||
|
def async_setup_preview(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up preview."""
|
||||||
|
preview_calls.append(None)
|
||||||
|
|
||||||
|
mock_integration(hass, MockModule("test"))
|
||||||
|
mock_entity_platform(hass, "config_flow.test", None)
|
||||||
|
|
||||||
|
assert len(preview_calls) == 0
|
||||||
|
|
||||||
|
with patch.dict(
|
||||||
|
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
|
||||||
|
):
|
||||||
|
result = await manager.flow.async_init("test", context={"source": "test1"})
|
||||||
|
|
||||||
|
assert len(preview_calls) == 0
|
||||||
|
assert result["preview"] is None
|
||||||
|
|
||||||
|
result = await manager.flow.async_init("test", context={"source": "test2"})
|
||||||
|
|
||||||
|
assert len(preview_calls) == 1
|
||||||
|
assert result["preview"] == "test"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_preview_not_supported(
|
||||||
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
||||||
|
) -> None:
|
||||||
|
"""Test preview support."""
|
||||||
|
|
||||||
|
class MockFlowHandler(config_entries.ConfigFlow):
|
||||||
|
"""Define a mock flow handler."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(self, data):
|
||||||
|
"""Mock Reauth."""
|
||||||
|
return self.async_show_form(step_id="user_confirm")
|
||||||
|
|
||||||
|
mock_integration(hass, MockModule("test"))
|
||||||
|
mock_entity_platform(hass, "config_flow.test", None)
|
||||||
|
|
||||||
|
with patch.dict(
|
||||||
|
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
|
||||||
|
):
|
||||||
|
result = await manager.flow.async_init(
|
||||||
|
"test", context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["preview"] is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user