Compare commits

..

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] c7ab3a8114 Merge origin/dev and resolve conflict 2026-05-25 12:07:13 +00:00
Erik d4ff05ed69 Update tests 2026-05-25 07:03:58 +02:00
Erik 802b932d3b Update add_suggested_values_to_schema 2026-05-25 07:03:51 +02:00
Erik ba297e35c6 Remove support for advanced mode from schema config flow 2026-05-25 06:51:40 +02:00
22 changed files with 101 additions and 310 deletions
@@ -17,7 +17,7 @@
"requirements": [
"bleak==3.0.2",
"bleak-retry-connector==4.6.1",
"bluetooth-adapters==2.2.0",
"bluetooth-adapters==2.1.0",
"bluetooth-auto-recovery==1.6.4",
"bluetooth-data-tools==1.29.18",
"dbus-fast==5.0.9",
@@ -8,7 +8,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["aioharmony", "slixmpp"],
"requirements": ["aioharmony==1.0.8"],
"requirements": ["aioharmony==1.0.3"],
"ssdp": [
{
"deviceType": "urn:myharmony-com:device:harmony:1",
@@ -36,5 +36,5 @@
"documentation": "https://www.home-assistant.io/integrations/led_ble",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["bluetooth-data-tools==1.29.18", "led-ble==1.1.11"]
"requirements": ["bluetooth-data-tools==1.29.18", "led-ble==1.1.8"]
}
@@ -8,6 +8,6 @@
"integration_type": "service",
"iot_class": "local_push",
"quality_scale": "silver",
"requirements": ["mcp==1.26.0", "aiohttp_sse==2.2.0", "anyio==4.13.0"],
"requirements": ["mcp==1.26.0", "aiohttp_sse==2.2.0", "anyio==4.10.0"],
"single_config_entry": true
}
@@ -0,0 +1,7 @@
"""Command names for the Novy Cooker Hood RF codes."""
from typing import Final
COMMAND_LIGHT: Final = "light"
COMMAND_PLUS: Final = "plus"
COMMAND_MINUS: Final = "minus"
@@ -3,7 +3,7 @@
import asyncio
from typing import Any
from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton
from rf_protocols.codes.novy.cooker_hood import get_codes_for_code
import voluptuous as vol
from homeassistant.components.radio_frequency import (
@@ -19,6 +19,7 @@ from homeassistant.const import CONF_CODE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, selector
from .commands import COMMAND_LIGHT
from .const import (
CODE_MAX,
CODE_MIN,
@@ -127,8 +128,10 @@ class NovyCookerHoodConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Toggle the hood light on then off so it ends in its starting state."""
assert self._transmitter_entity_id is not None
command = NovyCookerHoodButton.LIGHT.to_command(channel=self._code)
try:
command = await get_codes_for_code(self._code).async_load_command(
COMMAND_LIGHT
)
await async_send_command(self.hass, self._transmitter_entity_id, command)
await asyncio.sleep(_TOGGLE_GAP)
await async_send_command(self.hass, self._transmitter_entity_id, command)
@@ -3,8 +3,7 @@
import math
from typing import Any
from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton
from rf_protocols.commands.novy import NovyCookerHoodCommand
from rf_protocols.codes.novy.cooker_hood import get_codes_for_code
from homeassistant.components.fan import ATTR_PERCENTAGE, FanEntity, FanEntityFeature
from homeassistant.components.radio_frequency import async_send_command
@@ -18,6 +17,7 @@ from homeassistant.util.percentage import (
ranged_value_to_percentage,
)
from .commands import COMMAND_MINUS, COMMAND_PLUS
from .const import SPEED_COUNT
from .entity import NovyCookerHoodEntity
@@ -49,7 +49,7 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity):
def __init__(self, entry: ConfigEntry) -> None:
"""Initialize the fan."""
super().__init__(entry)
self._code: int = entry.data[CONF_CODE]
self._codes = get_codes_for_code(entry.data[CONF_CODE])
self._level = 0
self._attr_unique_id = entry.entry_id
@@ -103,7 +103,7 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity):
async def async_increase_speed(self, percentage_step: int | None = None) -> None:
"""Bump speed up by N hardware levels (no recalibration)."""
steps = self._steps_from_percentage(percentage_step)
plus = NovyCookerHoodButton.PLUS.to_command(channel=self._code)
plus = await self._codes.async_load_command(COMMAND_PLUS)
for _ in range(steps):
await self._async_send(plus)
self._level = min(SPEED_COUNT, self._level + steps)
@@ -112,7 +112,7 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity):
async def async_decrease_speed(self, percentage_step: int | None = None) -> None:
"""Bump speed down by N hardware levels (no recalibration)."""
steps = self._steps_from_percentage(percentage_step)
minus = NovyCookerHoodButton.MINUS.to_command(channel=self._code)
minus = await self._codes.async_load_command(COMMAND_MINUS)
for _ in range(steps):
await self._async_send(minus)
self._level = max(0, self._level - steps)
@@ -127,17 +127,17 @@ class NovyCookerHoodFan(NovyCookerHoodEntity, FanEntity, RestoreEntity):
async def _async_set_level(self, level: int) -> None:
"""Reset to off with `SPEED_COUNT` minus presses, then climb to level."""
minus = NovyCookerHoodButton.MINUS.to_command(channel=self._code)
minus = await self._codes.async_load_command(COMMAND_MINUS)
for _ in range(SPEED_COUNT):
await self._async_send(minus)
if level > 0:
plus = NovyCookerHoodButton.PLUS.to_command(channel=self._code)
plus = await self._codes.async_load_command(COMMAND_PLUS)
for _ in range(level):
await self._async_send(plus)
self._level = level
self.async_write_ha_state()
async def _async_send(self, command: NovyCookerHoodCommand) -> None:
async def _async_send(self, command: Any) -> None:
"""Send a single RF command via the configured transmitter."""
await async_send_command(
self.hass, self._transmitter, command, context=self._context
@@ -2,7 +2,7 @@
from typing import Any
from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton
from rf_protocols.codes.novy.cooker_hood import get_codes_for_code
from homeassistant.components.light import ColorMode, LightEntity
from homeassistant.components.radio_frequency import async_send_command
@@ -12,6 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from .commands import COMMAND_LIGHT
from .entity import NovyCookerHoodEntity
PARALLEL_UPDATES = 1
@@ -36,7 +37,7 @@ class NovyCookerHoodLight(NovyCookerHoodEntity, LightEntity, RestoreEntity):
def __init__(self, entry: ConfigEntry) -> None:
"""Initialize the light."""
super().__init__(entry)
self._code = entry.data[CONF_CODE]
self._codes = get_codes_for_code(entry.data[CONF_CODE])
self._attr_unique_id = entry.entry_id
async def async_added_to_hass(self) -> None:
@@ -47,19 +48,19 @@ class NovyCookerHoodLight(NovyCookerHoodEntity, LightEntity, RestoreEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on by sending the toggle command."""
await self._async_send_light()
await self._async_send_command(COMMAND_LIGHT)
self._attr_is_on = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off by sending the toggle command."""
await self._async_send_light()
await self._async_send_command(COMMAND_LIGHT)
self._attr_is_on = False
self.async_write_ha_state()
async def _async_send_light(self) -> None:
"""Send the light toggle command via the configured transmitter."""
command = NovyCookerHoodButton.LIGHT.to_command(channel=self._code)
async def _async_send_command(self, name: str) -> None:
"""Load the named command and send it via the configured transmitter."""
command = await self._codes.async_load_command(name)
await async_send_command(
self.hass, self._transmitter, command, context=self._context
)
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/radio_frequency",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["rf-protocols==4.0.0"]
"requirements": ["rf-protocols==3.2.0"]
}
@@ -39,7 +39,7 @@
"getmac==0.9.5",
"samsungctl[websocket]==0.7.1",
"samsungtvws[async,encrypted]==2.7.2",
"wakeonlan==3.3.0",
"wakeonlan==3.1.0",
"async-upnp-client==0.46.2"
],
"ssdp": [
@@ -17,7 +17,7 @@
"iot_class": "local_push",
"loggers": ["aioshelly"],
"quality_scale": "platinum",
"requirements": ["aioshelly==13.26.1"],
"requirements": ["aioshelly==13.26.0"],
"zeroconf": [
{
"name": "shelly*",
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/wake_on_lan",
"integration_type": "service",
"iot_class": "local_push",
"requirements": ["wakeonlan==3.3.0"]
"requirements": ["wakeonlan==3.1.0"]
}
-9
View File
@@ -658,15 +658,6 @@ class FlowHandler(Generic[_FlowContextT, _FlowResultT, _HandlerT]):
"""
schema = {}
for key, val in data_schema.schema.items():
if isinstance(key, vol.Marker):
# Exclude advanced field
if (
key.description
and key.description.get("advanced")
and not self.show_advanced_options
):
continue
# Process the section schema options
if (
suggested_values is not None
@@ -180,25 +180,7 @@ class SchemaCommonFlowHandler:
) -> ConfigFlowResult:
"""Handle a form step."""
form_step: SchemaFlowFormStep = cast(SchemaFlowFormStep, self._flow[step_id])
if (
user_input is not None
and (data_schema := await self._get_schema(form_step))
and data_schema.schema
and not self._handler.show_advanced_options
):
# Add advanced field default if not set
for key in data_schema.schema:
if isinstance(key, (vol.Optional, vol.Required)):
if (
key.description
and key.description.get("advanced")
and key.default is not vol.UNDEFINED
and key not in self._options
):
user_input[str(key.schema)] = cast(
Callable[[], Any], key.default
)()
data_schema = await self._get_schema(form_step)
if user_input is not None and form_step.validate_user_input is not None:
# Do extra validation of user input
@@ -230,12 +212,6 @@ class SchemaCommonFlowHandler:
if (
isinstance(key, vol.Optional)
and key not in user_input
and not (
# don't remove advanced keys, if they are hidden
key.description
and key.description.get("advanced")
and not self._handler.show_advanced_options
)
and not (
# don't remove read_only keys
isinstance(data_schema.schema[key], selector.Selector)
+2 -2
View File
@@ -22,7 +22,7 @@ awesomeversion==25.8.0
bcrypt==5.0.0
bleak-retry-connector==4.6.1
bleak==3.0.2
bluetooth-adapters==2.2.0
bluetooth-adapters==2.1.0
bluetooth-auto-recovery==1.6.4
bluetooth-data-tools==1.29.18
cached-ipaddress==1.1.1
@@ -113,7 +113,7 @@ uuid==1000000000.0.0
# even newer versions seem to introduce new issues, it's useful
# for us to pin all these
# requirements so we can directly link HA versions to these library versions.
anyio==4.13.0
anyio==4.10.0
h11==0.16.0
httpcore==1.0.9
+1 -1
View File
@@ -47,7 +47,7 @@ python-slugify==8.0.4
PyTurboJPEG==1.8.3
PyYAML==6.0.3
requests==2.34.2
rf-protocols==4.0.0
rf-protocols==3.2.0
securetar==2026.4.1
SQLAlchemy==2.0.49
standard-aifc==3.13.0
+7 -7
View File
@@ -279,7 +279,7 @@ aiogithubapi==26.0.0
aioguardian==2026.01.1
# homeassistant.components.harmony
aioharmony==1.0.8
aioharmony==1.0.3
# homeassistant.components.hassio
aiohasupervisor==0.4.3
@@ -405,7 +405,7 @@ aiorussound==5.0.1
aioruuvigateway==0.1.0
# homeassistant.components.shelly
aioshelly==13.26.1
aioshelly==13.26.0
# homeassistant.components.skybell
aioskybell==22.7.0
@@ -522,7 +522,7 @@ anthemav==1.4.1
anthropic==0.96.0
# homeassistant.components.mcp_server
anyio==4.13.0
anyio==4.10.0
# homeassistant.components.weatherkit
apple_weatherkit==1.1.3
@@ -675,7 +675,7 @@ bluecurrent-api==1.3.2
bluemaestro-ble==0.4.1
# homeassistant.components.bluetooth
bluetooth-adapters==2.2.0
bluetooth-adapters==2.1.0
# homeassistant.components.bluetooth
bluetooth-auto-recovery==1.6.4
@@ -1447,7 +1447,7 @@ ld2410-ble==0.1.1
leaone-ble==0.3.0
# homeassistant.components.led_ble
led-ble==1.1.11
led-ble==1.1.8
# homeassistant.components.lektrico
lektricowifi==0.1
@@ -2874,7 +2874,7 @@ renson-endura-delta==1.7.2
reolink-aio==0.20.0
# homeassistant.components.radio_frequency
rf-protocols==4.0.0
rf-protocols==3.2.0
# homeassistant.components.idteck_prox
rfk101py==0.0.1
@@ -3311,7 +3311,7 @@ vtjp==0.2.1
# homeassistant.components.samsungtv
# homeassistant.components.wake_on_lan
wakeonlan==3.3.0
wakeonlan==3.1.0
# homeassistant.components.wallbox
wallbox==0.9.0
+1 -1
View File
@@ -97,7 +97,7 @@ uuid==1000000000.0.0
# even newer versions seem to introduce new issues, it's useful
# for us to pin all these
# requirements so we can directly link HA versions to these library versions.
anyio==4.13.0
anyio==4.10.0
h11==0.16.0
httpcore==1.0.9
+32 -1
View File
@@ -1,6 +1,10 @@
"""Common fixtures for the Novy Cooker Hood tests."""
from collections.abc import Iterator
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from rf_protocols.loader import CodeCollection
from homeassistant.components.novy_cooker_hood.const import CONF_TRANSMITTER, DOMAIN
from homeassistant.const import CONF_CODE
@@ -8,11 +12,38 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry
from tests.components.radio_frequency.common import MockRadioFrequencyEntity
from tests.components.radio_frequency.common import (
MockRadioFrequencyCommand,
MockRadioFrequencyEntity,
)
TRANSMITTER_ENTITY_ID = "radio_frequency.test_rf_transmitter"
@pytest.fixture(autouse=True)
def mock_get_codes() -> Iterator[MagicMock]:
"""Patch the bundled-codes loader so tests don't hit the filesystem."""
fake_collection = MagicMock(spec=CodeCollection)
fake_collection.async_load_command = AsyncMock(
side_effect=lambda name: MockRadioFrequencyCommand()
)
with (
patch(
"homeassistant.components.novy_cooker_hood.light.get_codes_for_code",
return_value=fake_collection,
),
patch(
"homeassistant.components.novy_cooker_hood.fan.get_codes_for_code",
return_value=fake_collection,
),
patch(
"homeassistant.components.novy_cooker_hood.config_flow.get_codes_for_code",
return_value=fake_collection,
),
):
yield fake_collection
@pytest.fixture
def mock_config_entry(
mock_rf_entity: MockRadioFrequencyEntity,
@@ -1,11 +1,11 @@
"""Test the Novy Hood config flow."""
from collections.abc import Iterator
from unittest.mock import patch
from unittest.mock import MagicMock, patch
import pytest
from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton
from homeassistant.components.novy_cooker_hood.commands import COMMAND_LIGHT
from homeassistant.components.novy_cooker_hood.const import CONF_TRANSMITTER, DOMAIN
from homeassistant.components.radio_frequency import DATA_COMPONENT, DOMAIN as RF_DOMAIN
from homeassistant.config_entries import SOURCE_USER
@@ -49,6 +49,7 @@ async def _start_user_flow(hass: HomeAssistant, code: str = "1") -> dict:
async def test_user_flow_test_then_finish(
hass: HomeAssistant,
mock_get_codes: MagicMock,
mock_rf_entity: MockRadioFrequencyEntity,
entity_registry: er.EntityRegistry,
) -> None:
@@ -57,10 +58,8 @@ async def test_user_flow_test_then_finish(
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "test_light"
mock_get_codes.async_load_command.assert_awaited_with(COMMAND_LIGHT)
assert len(mock_rf_entity.send_command_calls) == 2
sent = mock_rf_entity.send_command_calls[0].command
assert sent.key == NovyCookerHoodButton.LIGHT.code
assert sent.channel == 3
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "finish"}
@@ -78,6 +77,7 @@ async def test_user_flow_test_then_finish(
async def test_user_flow_retry_picks_different_code(
hass: HomeAssistant,
mock_get_codes: MagicMock,
mock_rf_entity: MockRadioFrequencyEntity,
entity_registry: er.EntityRegistry,
) -> None:
@@ -99,13 +99,9 @@ async def test_user_flow_retry_picks_different_code(
},
)
assert result["type"] is FlowResultType.MENU
# One load per test x two tests; two sends per test x two tests.
assert mock_get_codes.async_load_command.await_count == 2
assert len(mock_rf_entity.send_command_calls) == 4
assert [c.command.channel for c in mock_rf_entity.send_command_calls] == [
1,
1,
7,
7,
]
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "finish"}
@@ -131,6 +127,7 @@ async def test_user_flow_test_transmit_failure(
async def test_recover_after_transmit_failure(
hass: HomeAssistant,
mock_get_codes: MagicMock,
mock_rf_entity: MockRadioFrequencyEntity,
) -> None:
"""The user can Retry from test_failed and complete the flow."""
@@ -186,6 +183,7 @@ async def test_unique_id_already_configured(
async def test_same_transmitter_different_code_is_allowed(
hass: HomeAssistant,
mock_get_codes: MagicMock,
mock_config_entry: MockConfigEntry,
mock_rf_entity: MockRadioFrequencyEntity,
entity_registry: er.EntityRegistry,
@@ -207,6 +205,7 @@ async def test_same_transmitter_different_code_is_allowed(
async def test_reconfigure_updates_entry(
hass: HomeAssistant,
mock_get_codes: MagicMock,
init_novy_cooker_hood: MockConfigEntry,
mock_rf_entity: MockRadioFrequencyEntity,
entity_registry: er.EntityRegistry,
@@ -225,9 +224,7 @@ async def test_reconfigure_updates_entry(
)
assert result["type"] is FlowResultType.MENU
assert result["step_id"] == "test_light"
sent = mock_rf_entity.send_command_calls[-1].command
assert sent.key == NovyCookerHoodButton.LIGHT.code
assert sent.channel == 4
mock_get_codes.async_load_command.assert_awaited_with(COMMAND_LIGHT)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={"next_step_id": "finish"}
@@ -242,6 +239,7 @@ async def test_reconfigure_updates_entry(
async def test_reconfigure_frees_old_unique_id(
hass: HomeAssistant,
mock_get_codes: MagicMock,
init_novy_cooker_hood: MockConfigEntry,
mock_rf_entity: MockRadioFrequencyEntity,
) -> None:
@@ -297,6 +295,7 @@ async def test_reconfigure_aborts_on_collision(
async def test_reconfigure_retry_returns_to_picker(
hass: HomeAssistant,
mock_get_codes: MagicMock,
init_novy_cooker_hood: MockConfigEntry,
mock_rf_entity: MockRadioFrequencyEntity,
) -> None:
@@ -327,6 +326,7 @@ async def test_no_transmitters(hass: HomeAssistant) -> None:
async def test_recover_after_no_transmitters(
hass: HomeAssistant,
mock_get_codes: MagicMock,
) -> None:
"""User can re-init the flow after the radio_frequency integration loads."""
result = await hass.config_entries.flow.async_init(
@@ -1,12 +1,13 @@
"""Tests for the Novy Hood light platform."""
from rf_protocols.codes.novy.cooker_hood import NovyCookerHoodButton
from unittest.mock import MagicMock, call
from homeassistant.components.light import (
DOMAIN as LIGHT_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.components.novy_cooker_hood.commands import COMMAND_LIGHT
from homeassistant.const import (
ATTR_ASSUMED_STATE,
ATTR_ENTITY_ID,
@@ -27,6 +28,7 @@ ENTITY_ID = "light.novy_cooker_hood_light"
async def test_turn_on_and_off_send_light_once_each(
hass: HomeAssistant,
mock_get_codes: MagicMock,
mock_rf_entity: MockRadioFrequencyEntity,
init_novy_cooker_hood: MockConfigEntry,
) -> None:
@@ -64,11 +66,11 @@ async def test_turn_on_and_off_send_light_once_each(
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.state == STATE_OFF
assert len(mock_rf_entity.send_command_calls) == 2
assert [c.command.key for c in mock_rf_entity.send_command_calls] == [
NovyCookerHoodButton.LIGHT.code,
NovyCookerHoodButton.LIGHT.code,
assert mock_get_codes.async_load_command.await_args_list == [
call(COMMAND_LIGHT),
call(COMMAND_LIGHT),
]
assert len(mock_rf_entity.send_command_calls) == 2
async def test_restore_state(
@@ -104,213 +104,6 @@ async def test_name(hass: HomeAssistant, entity_registry: er.EntityRegistry) ->
assert wrapped_entity_config_entry_title(hass, entry.id) == "Custom Name"
@pytest.mark.parametrize("marker", [vol.Required, vol.Optional])
async def test_config_flow_advanced_option(
hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker
) -> None:
"""Test handling of advanced options in config flow."""
manager.hass = hass
CONFIG_SCHEMA = vol.Schema(
{
marker("option1"): str,
marker("advanced_no_default", description={"advanced": True}): str,
marker(
"advanced_default",
default="a very reasonable default",
description={"advanced": True},
): str,
}
)
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"init": SchemaFlowFormStep(CONFIG_SCHEMA)
}
@manager.mock_reg_handler("test")
class TestFlow(MockSchemaConfigFlowHandler):
config_flow = CONFIG_FLOW
# Start flow in basic mode
result = await manager.async_init("test")
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert list(result["data_schema"].schema.keys()) == ["option1"]
result = await manager.async_configure(result["flow_id"], {"option1": "blabla"})
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {}
assert result["options"] == {
"advanced_default": "a very reasonable default",
"option1": "blabla",
}
for option in result["options"]:
# Make sure we didn't get the Optional or Required instance as key
assert isinstance(option, str)
# Start flow in advanced mode
result = await manager.async_init("test", context={"show_advanced_options": True})
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert list(result["data_schema"].schema.keys()) == [
"option1",
"advanced_no_default",
"advanced_default",
]
result = await manager.async_configure(
result["flow_id"], {"advanced_no_default": "abc123", "option1": "blabla"}
)
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {}
assert result["options"] == {
"advanced_default": "a very reasonable default",
"advanced_no_default": "abc123",
"option1": "blabla",
}
for option in result["options"]:
# Make sure we didn't get the Optional or Required instance as key
assert isinstance(option, str)
# Start flow in advanced mode
result = await manager.async_init("test", context={"show_advanced_options": True})
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert list(result["data_schema"].schema.keys()) == [
"option1",
"advanced_no_default",
"advanced_default",
]
result = await manager.async_configure(
result["flow_id"],
{
"advanced_default": "not default",
"advanced_no_default": "abc123",
"option1": "blabla",
},
)
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {}
assert result["options"] == {
"advanced_default": "not default",
"advanced_no_default": "abc123",
"option1": "blabla",
}
for option in result["options"]:
# Make sure we didn't get the Optional or Required instance as key
assert isinstance(option, str)
@pytest.mark.parametrize("marker", [vol.Required, vol.Optional])
async def test_options_flow_advanced_option(
hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker
) -> None:
"""Test handling of advanced options in options flow."""
manager.hass = hass
OPTIONS_SCHEMA = vol.Schema(
{
marker("option1"): str,
marker("advanced_no_default", description={"advanced": True}): str,
marker(
"advanced_default",
default="a very reasonable default",
description={"advanced": True},
): str,
}
)
OPTIONS_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"init": SchemaFlowFormStep(OPTIONS_SCHEMA)
}
class TestFlow(MockSchemaConfigFlowHandler, domain="test"):
config_flow = {}
options_flow = OPTIONS_FLOW
mock_integration(hass, MockModule("test"))
mock_platform(hass, "test.config_flow", None)
config_entry = MockConfigEntry(
data={},
domain="test",
options={
"option1": "blabla",
"advanced_no_default": "abc123",
"advanced_default": "not default",
},
)
config_entry.add_to_hass(hass)
# Start flow in basic mode
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert list(result["data_schema"].schema.keys()) == ["option1"]
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"option1": "blublu"}
)
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {
"advanced_default": "not default",
"advanced_no_default": "abc123",
"option1": "blublu",
}
for option in result["data"]:
# Make sure we didn't get the Optional or Required instance as key
assert isinstance(option, str)
# Start flow in advanced mode
result = await hass.config_entries.options.async_init(
config_entry.entry_id, context={"show_advanced_options": True}
)
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert list(result["data_schema"].schema.keys()) == [
"option1",
"advanced_no_default",
"advanced_default",
]
result = await hass.config_entries.options.async_configure(
result["flow_id"], {"advanced_no_default": "def456", "option1": "blabla"}
)
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {
"advanced_default": "a very reasonable default",
"advanced_no_default": "def456",
"option1": "blabla",
}
for option in result["data"]:
# Make sure we didn't get the Optional or Required instance as key
assert isinstance(option, str)
# Start flow in advanced mode
result = await hass.config_entries.options.async_init(
config_entry.entry_id, context={"show_advanced_options": True}
)
assert result["type"] is data_entry_flow.FlowResultType.FORM
assert list(result["data_schema"].schema.keys()) == [
"option1",
"advanced_no_default",
"advanced_default",
]
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{
"advanced_default": "also not default",
"advanced_no_default": "abc123",
"option1": "blabla",
},
)
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {
"advanced_default": "also not default",
"advanced_no_default": "abc123",
"option1": "blabla",
}
for option in result["data"]:
# Make sure we didn't get the Optional or Required instance as key
assert isinstance(option, str)
async def test_menu_step(hass: HomeAssistant) -> None:
"""Test menu step."""
@@ -722,12 +515,6 @@ async def test_options_flow_omit_optional_keys(
{
vol.Optional("optional_no_default"): str,
vol.Optional("optional_default", default="a very reasonable default"): str,
vol.Optional("advanced_no_default", description={"advanced": True}): str,
vol.Optional(
"advanced_default",
default="a very reasonable default",
description={"advanced": True},
): str,
}
)
@@ -747,8 +534,6 @@ async def test_options_flow_omit_optional_keys(
options={
"optional_no_default": "abc123",
"optional_default": "not default",
"advanced_no_default": "abc123",
"advanced_default": "not default",
},
)
config_entry.add_to_hass(hass)
@@ -764,8 +549,6 @@ async def test_options_flow_omit_optional_keys(
result = await hass.config_entries.options.async_configure(result["flow_id"], {})
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {
"advanced_default": "not default",
"advanced_no_default": "abc123",
"optional_default": "a very reasonable default",
}
@@ -777,14 +560,11 @@ async def test_options_flow_omit_optional_keys(
assert list(result["data_schema"].schema.keys()) == [
"optional_no_default",
"optional_default",
"advanced_no_default",
"advanced_default",
]
result = await hass.config_entries.options.async_configure(result["flow_id"], {})
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {
"advanced_default": "a very reasonable default",
"optional_default": "a very reasonable default",
}