Add preview to sensor group config and option flows (#83638)

This commit is contained in:
Erik Montnemery 2023-08-22 10:29:16 +02:00 committed by GitHub
parent 52b1e34af0
commit b885dfa5a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 483 additions and 45 deletions

View File

@ -7,8 +7,10 @@ from typing import Any, cast
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.const import CONF_ENTITIES, CONF_TYPE
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, selector
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
@ -22,6 +24,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
from . import DOMAIN
from .binary_sensor import CONF_ALL
from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC
from .sensor import SensorGroup
_STATISTIC_MEASURES = [
"min",
@ -36,15 +39,22 @@ _STATISTIC_MEASURES = [
async def basic_group_options_schema(
domain: str | list[str], handler: SchemaCommonFlowHandler
domain: str | list[str], handler: SchemaCommonFlowHandler | None
) -> vol.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(
{
vol.Required(CONF_ENTITIES): entity_selector_without_own_entities(
cast(SchemaOptionsFlowHandler, handler.parent_handler),
selector.EntitySelectorConfig(domain=domain, multiple=True),
),
vol.Required(CONF_ENTITIES): entity_selector,
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
}
)
@ -96,7 +106,7 @@ SENSOR_OPTIONS = {
async def sensor_options_schema(
domain: str, handler: SchemaCommonFlowHandler
domain: str, handler: SchemaCommonFlowHandler | None
) -> vol.Schema:
"""Generate options schema."""
return (
@ -184,6 +194,7 @@ CONFIG_FLOW = {
"sensor": SchemaFlowFormStep(
SENSOR_CONFIG_SCHEMA,
validate_user_input=set_group_type("sensor"),
preview="group_sensor",
),
"switch": SchemaFlowFormStep(
basic_group_config_schema("switch"),
@ -202,7 +213,10 @@ OPTIONS_FLOW = {
"media_player": SchemaFlowFormStep(
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")),
}
@ -241,6 +255,12 @@ class GroupConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
)
_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(
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:
continue
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
)

View File

@ -1,7 +1,7 @@
"""Platform allowing several sensors to be grouped into one sensor to provide numeric combinations."""
from __future__ import annotations
from collections.abc import Callable
from collections.abc import Callable, Mapping
from datetime import datetime
import logging
import statistics
@ -33,7 +33,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
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.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import (
@ -303,6 +303,26 @@ class SensorGroup(GroupEntity, SensorEntity):
self._state_incorrect: set[str] = set()
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:
"""Register callbacks."""

View File

@ -1814,6 +1814,14 @@ class ConfigFlow(data_entry_flow.FlowHandler):
class OptionsFlowManager(data_entry_flow.FlowManager):
"""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(
self,
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 = self.hass.config_entries.async_get_entry(handler_key)
if entry is None:
raise UnknownEntry(handler_key)
entry = self._async_get_config_entry(handler_key)
handler = await _async_get_flow_handler(self.hass, entry.domain, {})
return handler.async_get_options_flow(entry)
@ -1853,6 +1858,14 @@ class OptionsFlowManager(data_entry_flow.FlowManager):
result["result"] = True
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):
"""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")
async def _async_get_flow_handler(
async def _load_integration(
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
) -> None:
try:
integration = await loader.async_get_integration(hass, domain)
except loader.IntegrationNotFound as err:
@ -2044,6 +2051,18 @@ async def _async_get_flow_handler(
)
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):
return handler

View File

@ -95,6 +95,7 @@ class FlowResult(TypedDict, total=False):
last_step: bool | None
menu_options: list[str] | dict[str, str]
options: Mapping[str, Any]
preview: str | None
progress_action: str
reason: str
required: bool
@ -135,6 +136,7 @@ class FlowManager(abc.ABC):
) -> None:
"""Initialize the flow manager."""
self.hass = hass
self._preview: set[str] = set()
self._progress: dict[str, FlowHandler] = {}
self._handler_progress_index: dict[str, 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
)
# 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):
result["type"] = FlowResultType(result["type"]) # type: ignore[unreachable]
report(
@ -429,6 +435,12 @@ class FlowManager(abc.ABC):
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:
"""Handle a data entry flow."""
@ -504,6 +516,7 @@ class FlowHandler:
errors: dict[str, str] | None = None,
description_placeholders: Mapping[str, str | None] | None = None,
last_step: bool | None = None,
preview: str | None = None,
) -> FlowResult:
"""Return the definition of a form to gather user input."""
return FlowResult(
@ -515,6 +528,7 @@ class FlowHandler:
errors=errors,
description_placeholders=description_placeholders,
last_step=last_step, # Display next or submit button in frontend
preview=preview, # Display preview component in frontend
)
@callback
@ -635,6 +649,11 @@ class FlowHandler:
def async_remove(self) -> None:
"""Notification that the flow has been removed."""
@callback
@staticmethod
def async_setup_preview(hass: HomeAssistant) -> None:
"""Set up preview."""
@callback
def _create_abort_data(

View File

@ -756,31 +756,10 @@ class Entity(ABC):
return f"{device_name} {name}" if device_name else name
@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
def _async_generate_attributes(self) -> tuple[str, dict[str, Any]]:
"""Calculate state string and attribute mapping."""
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 = dict(attr) if attr else {}
@ -818,6 +797,33 @@ class Entity(ABC):
if (supported_features := self.supported_features) is not None:
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()
if end - start > 0.4 and not self._slow_reported:

View File

@ -78,6 +78,9 @@ class SchemaFlowFormStep(SchemaFlowStep):
have priority over the suggested values.
"""
preview: str | None = None
"""Optional preview component."""
@dataclass(slots=True)
class SchemaFlowMenuStep(SchemaFlowStep):
@ -237,6 +240,7 @@ class SchemaCommonFlowHandler:
data_schema=data_schema,
errors=errors,
last_step=last_step,
preview=form_step.preview,
)
async def _async_menu_step(
@ -271,7 +275,10 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC):
raise UnknownHandler
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
@ -285,6 +292,11 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC):
"""Initialize config flow."""
self._common_handler = SchemaCommonFlowHandler(self, self.config_flow, None)
@callback
@staticmethod
def async_setup_preview(hass: HomeAssistant) -> None:
"""Set up preview."""
@classmethod
@callback
def async_supports_options_flow(
@ -357,6 +369,7 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry):
options_flow: Mapping[str, SchemaFlowStep],
async_options_flow_finished: Callable[[HomeAssistant, Mapping[str, Any]], None]
| None = None,
async_setup_preview: Callable[[HomeAssistant], None] | None = None,
) -> None:
"""Initialize options flow.
@ -378,6 +391,9 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry):
types.MethodType(self._async_step(step), self),
)
if async_setup_preview:
setattr(self, "async_setup_preview", async_setup_preview)
@staticmethod
def _async_step(step_id: str) -> Callable:
"""Generate a step handler."""

View File

@ -123,6 +123,7 @@ async def test_legacy_subscription_repair_flow(
"errors": None,
"description_placeholders": None,
"last_step": None,
"preview": None,
}
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,
"description_placeholders": None,
"last_step": None,
"preview": None,
}
with patch("homeassistant.components.cloud.repairs.MAX_RETRIES", new=0):

View File

@ -396,6 +396,7 @@ async def test_initialize_flow(hass: HomeAssistant, client) -> None:
},
"errors": {"username": "Should be unique."},
"last_step": None,
"preview": None,
}
@ -571,6 +572,7 @@ async def test_two_step_flow(
"description_placeholders": None,
"errors": None,
"last_step": None,
"preview": None,
}
with patch.dict(HANDLERS, {"test": TestFlow}):
@ -647,6 +649,7 @@ async def test_continue_flow_unauth(
"description_placeholders": None,
"errors": None,
"last_step": None,
"preview": None,
}
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"},
"errors": None,
"last_step": None,
"preview": None,
}
@ -917,6 +921,7 @@ async def test_two_step_options_flow(hass: HomeAssistant, client) -> None:
"description_placeholders": None,
"errors": None,
"last_step": None,
"preview": None,
}
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,
"errors": None,
"last_step": None,
"preview": None,
}
with patch.dict(HANDLERS, {"test": TestFlow}):

View File

@ -9,6 +9,7 @@
'flow_id': <ANY>,
'handler': 'gardena_bluetooth',
'last_step': None,
'preview': None,
'step_id': 'confirm',
'type': <FlowResultType.FORM: 'form'>,
})
@ -136,6 +137,7 @@
'flow_id': <ANY>,
'handler': 'gardena_bluetooth',
'last_step': None,
'preview': None,
'step_id': 'user',
'type': <FlowResultType.FORM: 'form'>,
})
@ -150,6 +152,7 @@
'flow_id': <ANY>,
'handler': 'gardena_bluetooth',
'last_step': None,
'preview': None,
'step_id': 'confirm',
'type': <FlowResultType.FORM: 'form'>,
})
@ -198,6 +201,7 @@
'flow_id': <ANY>,
'handler': 'gardena_bluetooth',
'last_step': None,
'preview': None,
'step_id': 'user',
'type': <FlowResultType.FORM: 'form'>,
})
@ -212,6 +216,7 @@
'flow_id': <ANY>,
'handler': 'gardena_bluetooth',
'last_step': None,
'preview': None,
'step_id': 'confirm',
'type': <FlowResultType.FORM: 'form'>,
})

View File

@ -10,6 +10,7 @@ from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry
from tests.typing import WebSocketGenerator
@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}.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"}

View File

@ -84,6 +84,7 @@ async def test_supervisor_issue_repair_flow(
"errors": None,
"description_placeholders": {"reference": "/dev/sda1"},
"last_step": True,
"preview": None,
}
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,
"description_placeholders": None,
"last_step": True,
"preview": None,
}
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,
"description_placeholders": None,
"last_step": True,
"preview": None,
}
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,
"description_placeholders": {"components": "Home Assistant\n- test"},
"last_step": True,
"preview": None,
}
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")

View File

@ -212,6 +212,7 @@ async def test_issues_created(
"flow_id": ANY,
"handler": DOMAIN,
"last_step": None,
"preview": None,
"step_id": "confirm",
"type": "form",
}

View File

@ -313,6 +313,7 @@ async def test_fix_issue(
"flow_id": ANY,
"handler": domain,
"last_step": None,
"preview": None,
"step_id": step,
"type": "form",
}

View File

@ -264,6 +264,7 @@ async def test_pin_form_init(pin_form) -> None:
"step_id": "pin",
"type": "form",
"last_step": None,
"preview": None,
}
assert pin_form == expected

View File

@ -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(
hass: HomeAssistant, manager: config_entries.ConfigEntries
) -> None:
@ -3919,3 +3940,75 @@ async def test_task_tracking(hass: HomeAssistant) -> None:
hass.loop.call_soon(event.set)
await entry._async_process_on_unload(hass)
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