Fix empty actions (#142292)

* Apply fix

* Add tests for alarm button cover lock

* update light

* add number tests

* test select

* add switch tests

* test vacuum

* update lock test
This commit is contained in:
Petro31 2025-04-04 16:17:52 -04:00 committed by Franck Nijhof
parent 8d62cb60a6
commit c25f26a290
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
18 changed files with 613 additions and 19 deletions

View File

@ -214,7 +214,8 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, Restore
),
(CONF_TRIGGER_ACTION, AlarmControlPanelEntityFeature.TRIGGER),
):
if action_config := config.get(action_id):
# Scripts can be an empty list, therefore we need to check for None
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
self._attr_supported_features |= supported_feature

View File

@ -120,7 +120,8 @@ class TemplateButtonEntity(TemplateEntity, ButtonEntity):
"""Initialize the button."""
super().__init__(hass, config=config, unique_id=unique_id)
assert self._attr_name is not None
if action := config.get(CONF_PRESS):
# Scripts can be an empty list, therefore we need to check for None
if (action := config.get(CONF_PRESS)) is not None:
self.add_script(CONF_PRESS, action, self._attr_name, DOMAIN)
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
self._attr_state = None

View File

@ -172,7 +172,8 @@ class CoverTemplate(TemplateEntity, CoverEntity):
(POSITION_ACTION, CoverEntityFeature.SET_POSITION),
(TILT_ACTION, TILT_FEATURES),
):
if action_config := config.get(action_id):
# Scripts can be an empty list, therefore we need to check for None
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
self._attr_supported_features |= supported_feature

View File

@ -157,7 +157,8 @@ class TemplateFan(TemplateEntity, FanEntity):
CONF_SET_OSCILLATING_ACTION,
CONF_SET_DIRECTION_ACTION,
):
if action_config := config.get(action_id):
# Scripts can be an empty list, therefore we need to check for None
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
self._state: bool | None = False

View File

@ -296,7 +296,8 @@ class LightTemplate(TemplateEntity, LightEntity):
self._supports_transition_template = config.get(CONF_SUPPORTS_TRANSITION)
for action_id in (CONF_ON_ACTION, CONF_OFF_ACTION, CONF_EFFECT_ACTION):
if action_config := config.get(action_id):
# Scripts can be an empty list, therefore we need to check for None
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
self._state = False
@ -323,7 +324,8 @@ class LightTemplate(TemplateEntity, LightEntity):
(CONF_RGBW_ACTION, ColorMode.RGBW),
(CONF_RGBWW_ACTION, ColorMode.RGBWW),
):
if action_config := config.get(action_id):
# Scripts can be an empty list, therefore we need to check for None
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
color_modes.add(color_mode)
self._supported_color_modes = filter_supported_color_modes(color_modes)
@ -333,7 +335,7 @@ class LightTemplate(TemplateEntity, LightEntity):
self._color_mode = next(iter(self._supported_color_modes))
self._attr_supported_features = LightEntityFeature(0)
if self._action_scripts.get(CONF_EFFECT_ACTION):
if (self._action_scripts.get(CONF_EFFECT_ACTION)) is not None:
self._attr_supported_features |= LightEntityFeature.EFFECT
if self._supports_transition is True:
self._attr_supported_features |= LightEntityFeature.TRANSITION

View File

@ -98,7 +98,8 @@ class TemplateLock(TemplateEntity, LockEntity):
(CONF_UNLOCK, 0),
(CONF_OPEN, LockEntityFeature.OPEN),
):
if action_config := config.get(action_id):
# Scripts can be an empty list, therefore we need to check for None
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
self._attr_supported_features |= supported_feature
self._code_format_template = config.get(CONF_CODE_FORMAT_TEMPLATE)

View File

@ -141,7 +141,8 @@ class TemplateSelect(TemplateEntity, SelectEntity):
super().__init__(hass, config=config, unique_id=unique_id)
assert self._attr_name is not None
self._value_template = config[CONF_STATE]
if select_option := config.get(CONF_SELECT_OPTION):
# Scripts can be an empty list, therefore we need to check for None
if (select_option := config.get(CONF_SELECT_OPTION)) is not None:
self.add_script(CONF_SELECT_OPTION, select_option, self._attr_name, DOMAIN)
self._options_template = config[ATTR_OPTIONS]
self._attr_assumed_state = self._optimistic = config.get(CONF_OPTIMISTIC, False)
@ -197,7 +198,8 @@ class TriggerSelectEntity(TriggerEntity, SelectEntity):
) -> None:
"""Initialize the entity."""
super().__init__(hass, coordinator, config)
if select_option := config.get(CONF_SELECT_OPTION):
# Scripts can be an empty list, therefore we need to check for None
if (select_option := config.get(CONF_SELECT_OPTION)) is not None:
self.add_script(
CONF_SELECT_OPTION,
select_option,

View File

@ -226,9 +226,10 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
assert name is not None
self._template = config.get(CONF_STATE)
if on_action := config.get(CONF_TURN_ON):
# Scripts can be an empty list, therefore we need to check for None
if (on_action := config.get(CONF_TURN_ON)) is not None:
self.add_script(CONF_TURN_ON, on_action, name, DOMAIN)
if off_action := config.get(CONF_TURN_OFF):
if (off_action := config.get(CONF_TURN_OFF)) is not None:
self.add_script(CONF_TURN_OFF, off_action, name, DOMAIN)
self._state: bool | None = False

View File

@ -158,7 +158,8 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
(SERVICE_LOCATE, VacuumEntityFeature.LOCATE),
(SERVICE_SET_FAN_SPEED, VacuumEntityFeature.FAN_SPEED),
):
if action_config := config.get(action_id):
# Scripts can be an empty list, therefore we need to check for None
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
self._attr_supported_features |= supported_feature

View File

@ -82,6 +82,15 @@ OPTIMISTIC_TEMPLATE_ALARM_CONFIG = {
"data": {"code": "{{ this.entity_id }}"},
},
}
EMPTY_ACTIONS = {
"arm_away": [],
"arm_home": [],
"arm_night": [],
"arm_vacation": [],
"arm_custom_bypass": [],
"disarm": [],
"trigger": [],
}
TEMPLATE_ALARM_CONFIG = {
@ -173,6 +182,12 @@ async def test_setup_config_entry(
"panels": {"test_template_panel": OPTIMISTIC_TEMPLATE_ALARM_CONFIG},
}
},
{
"alarm_control_panel": {
"platform": "template",
"panels": {"test_template_panel": EMPTY_ACTIONS},
}
},
],
)
@pytest.mark.usefixtures("start_ha")

View File

@ -93,6 +93,45 @@ async def test_missing_optional_config(hass: HomeAssistant) -> None:
_verify(hass, STATE_UNKNOWN)
async def test_missing_emtpy_press_action_config(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test: missing optional template is ok."""
with assert_setup_component(1, "template"):
assert await setup.async_setup_component(
hass,
"template",
{
"template": {
"button": {
"press": [],
},
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
_verify(hass, STATE_UNKNOWN)
now = dt.datetime.now(dt.UTC)
freezer.move_to(now)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{CONF_ENTITY_ID: _TEST_BUTTON},
blocking=True,
)
_verify(
hass,
now.isoformat(),
)
async def test_missing_required_keys(hass: HomeAssistant) -> None:
"""Test: missing required fields will fail."""
with assert_setup_component(0, "template"):

View File

@ -9,6 +9,7 @@ from homeassistant.components.cover import (
ATTR_POSITION,
ATTR_TILT_POSITION,
DOMAIN as COVER_DOMAIN,
CoverEntityFeature,
CoverState,
)
from homeassistant.const import (
@ -28,6 +29,7 @@ from homeassistant.const import (
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.setup import async_setup_component
from tests.common import assert_setup_component
@ -1123,3 +1125,50 @@ async def test_self_referencing_icon_with_no_template_is_not_a_loop(
assert len(hass.states.async_all()) == 1
assert "Template loop detected" not in caplog.text
@pytest.mark.parametrize(
("script", "supported_feature"),
[
("stop_cover", CoverEntityFeature.STOP),
("set_cover_position", CoverEntityFeature.SET_POSITION),
(
"set_cover_tilt_position",
CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.STOP_TILT
| CoverEntityFeature.SET_TILT_POSITION,
),
],
)
async def test_emtpy_action_config(
hass: HomeAssistant, script: str, supported_feature: CoverEntityFeature
) -> None:
"""Test configuration with empty script."""
with assert_setup_component(1, COVER_DOMAIN):
assert await async_setup_component(
hass,
COVER_DOMAIN,
{
COVER_DOMAIN: {
"platform": "template",
"covers": {
"test_template_cover": {
"open_cover": [],
"close_cover": [],
script: [],
}
},
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("cover.test_template_cover")
assert (
state.attributes["supported_features"]
== CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | supported_feature
)

View File

@ -556,6 +556,42 @@ async def setup_single_action_light(
)
@pytest.fixture
async def setup_empty_action_light(
hass: HomeAssistant,
count: int,
style: ConfigurationStyle,
action: str,
extra_config: dict,
) -> None:
"""Do setup of light integration."""
if style == ConfigurationStyle.LEGACY:
await async_setup_legacy_format(
hass,
count,
{
"test_template_light": {
"turn_on": [],
"turn_off": [],
action: [],
**extra_config,
}
},
)
elif style == ConfigurationStyle.MODERN:
await async_setup_new_format(
hass,
count,
{
"name": "test_template_light",
"turn_on": [],
"turn_off": [],
action: [],
**extra_config,
},
)
@pytest.fixture
async def setup_light_with_effects(
hass: HomeAssistant,
@ -2404,3 +2440,82 @@ async def test_nested_unique_id(
entry = entity_registry.async_get("light.test_b")
assert entry
assert entry.unique_id == "x-b"
@pytest.mark.parametrize(("count", "extra_config"), [(1, {})])
@pytest.mark.parametrize(
"style",
[
ConfigurationStyle.LEGACY,
ConfigurationStyle.MODERN,
],
)
@pytest.mark.parametrize(
("action", "color_mode"),
[
("set_level", ColorMode.BRIGHTNESS),
("set_temperature", ColorMode.COLOR_TEMP),
("set_hs", ColorMode.HS),
("set_rgb", ColorMode.RGB),
("set_rgbw", ColorMode.RGBW),
("set_rgbww", ColorMode.RGBWW),
],
)
async def test_empty_color_mode_action_config(
hass: HomeAssistant,
color_mode: ColorMode,
setup_empty_action_light,
) -> None:
"""Test empty actions for color mode actions."""
state = hass.states.get("light.test_template_light")
assert state.attributes["supported_color_modes"] == [color_mode]
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "light.test_template_light"},
blocking=True,
)
state = hass.states.get("light.test_template_light")
assert state.state == STATE_ON
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.test_template_light"},
blocking=True,
)
state = hass.states.get("light.test_template_light")
assert state.state == STATE_OFF
@pytest.mark.parametrize(("count"), [1])
@pytest.mark.parametrize(
("style", "extra_config"),
[
(
ConfigurationStyle.LEGACY,
{
"effect_list_template": "{{ ['a'] }}",
"effect_template": "{{ 'a' }}",
},
),
(
ConfigurationStyle.MODERN,
{
"effect_list": "{{ ['a'] }}",
"effect": "{{ 'a' }}",
},
),
],
)
@pytest.mark.parametrize("action", ["set_effect"])
async def test_effect_with_empty_action(
hass: HomeAssistant,
setup_empty_action_light,
) -> None:
"""Test empty set_effect action."""
state = hass.states.get("light.test_template_light")
assert state.attributes["supported_features"] == LightEntityFeature.EFFECT

View File

@ -4,7 +4,7 @@ import pytest
from homeassistant import setup
from homeassistant.components import lock
from homeassistant.components.lock import LockState
from homeassistant.components.lock import LockEntityFeature, LockState
from homeassistant.const import (
ATTR_CODE,
ATTR_ENTITY_ID,
@ -15,6 +15,8 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, ServiceCall
from tests.common import assert_setup_component
OPTIMISTIC_LOCK_CONFIG = {
"platform": "template",
"lock": {
@ -718,3 +720,50 @@ async def test_unique_id(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
assert len(hass.states.async_all("lock")) == 1
async def test_emtpy_action_config(hass: HomeAssistant) -> None:
"""Test configuration with empty script."""
with assert_setup_component(1, lock.DOMAIN):
assert await setup.async_setup_component(
hass,
lock.DOMAIN,
{
lock.DOMAIN: {
"platform": "template",
"value_template": "{{ 0 == 1 }}",
"lock": [],
"unlock": [],
"open": [],
"name": "test_template_lock",
"optimistic": True,
},
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("lock.test_template_lock")
assert state.attributes["supported_features"] == LockEntityFeature.OPEN
await hass.services.async_call(
lock.DOMAIN,
lock.SERVICE_UNLOCK,
{ATTR_ENTITY_ID: "lock.test_template_lock"},
)
await hass.async_block_till_done()
state = hass.states.get("lock.test_template_lock")
assert state.state == LockState.UNLOCKED
await hass.services.async_call(
lock.DOMAIN,
lock.SERVICE_LOCK,
{ATTR_ENTITY_ID: "lock.test_template_lock"},
)
await hass.async_block_till_done()
state = hass.states.get("lock.test_template_lock")
assert state.state == LockState.LOCKED

View File

@ -1,8 +1,12 @@
"""The tests for the Template number platform."""
from typing import Any
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant import setup
from homeassistant.components import number, template
from homeassistant.components.input_number import (
ATTR_VALUE as INPUT_NUMBER_ATTR_VALUE,
DOMAIN as INPUT_NUMBER_DOMAIN,
@ -18,6 +22,7 @@ from homeassistant.components.number import (
)
from homeassistant.components.template import DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_ICON,
CONF_ENTITY_ID,
CONF_UNIT_OF_MEASUREMENT,
@ -25,10 +30,14 @@ from homeassistant.const import (
)
from homeassistant.core import Context, HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from .conftest import ConfigurationStyle
from tests.common import MockConfigEntry, assert_setup_component, async_capture_events
_TEST_NUMBER = "number.template_number"
_TEST_OBJECT_ID = "template_number"
_TEST_NUMBER = f"number.{_TEST_OBJECT_ID}"
# Represent for number's value
_VALUE_INPUT_NUMBER = "input_number.value"
# Represent for number's minimum
@ -50,6 +59,38 @@ _VALUE_INPUT_NUMBER_CONFIG = {
}
async def async_setup_modern_format(
hass: HomeAssistant, count: int, number_config: dict[str, Any]
) -> None:
"""Do setup of number integration via new format."""
config = {"template": {"number": number_config}}
with assert_setup_component(count, template.DOMAIN):
assert await async_setup_component(
hass,
template.DOMAIN,
config,
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
@pytest.fixture
async def setup_number(
hass: HomeAssistant,
count: int,
style: ConfigurationStyle,
number_config: dict[str, Any],
) -> None:
"""Do setup of number integration."""
if style == ConfigurationStyle.MODERN:
await async_setup_modern_format(
hass, count, {"name": _TEST_OBJECT_ID, **number_config}
)
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
@ -565,3 +606,36 @@ async def test_device_id(
template_entity = entity_registry.async_get("number.my_template")
assert template_entity is not None
assert template_entity.device_id == device_entry.id
@pytest.mark.parametrize(
("count", "number_config"),
[
(
1,
{
"state": "{{ 1 }}",
"set_value": [],
"step": "{{ 1 }}",
"optimistic": True,
},
)
],
)
@pytest.mark.parametrize(
"style",
[
ConfigurationStyle.MODERN,
],
)
async def test_empty_action_config(hass: HomeAssistant, setup_number) -> None:
"""Test configuration with empty script."""
await hass.services.async_call(
number.DOMAIN,
number.SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: _TEST_NUMBER, "value": 4},
blocking=True,
)
state = hass.states.get(_TEST_NUMBER)
assert float(state.state) == 4

View File

@ -1,8 +1,12 @@
"""The tests for the Template select platform."""
from typing import Any
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant import setup
from homeassistant.components import select, template
from homeassistant.components.input_select import (
ATTR_OPTION as INPUT_SELECT_ATTR_OPTION,
ATTR_OPTIONS as INPUT_SELECT_ATTR_OPTIONS,
@ -17,17 +21,53 @@ from homeassistant.components.select import (
SERVICE_SELECT_OPTION as SELECT_SERVICE_SELECT_OPTION,
)
from homeassistant.components.template import DOMAIN
from homeassistant.const import ATTR_ICON, CONF_ENTITY_ID, STATE_UNKNOWN
from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, CONF_ENTITY_ID, STATE_UNKNOWN
from homeassistant.core import Context, HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from .conftest import ConfigurationStyle
from tests.common import MockConfigEntry, assert_setup_component, async_capture_events
_TEST_SELECT = "select.template_select"
_TEST_OBJECT_ID = "template_select"
_TEST_SELECT = f"select.{_TEST_OBJECT_ID}"
# Represent for select's current_option
_OPTION_INPUT_SELECT = "input_select.option"
async def async_setup_modern_format(
hass: HomeAssistant, count: int, select_config: dict[str, Any]
) -> None:
"""Do setup of select integration via new format."""
config = {"template": {"select": select_config}}
with assert_setup_component(count, template.DOMAIN):
assert await async_setup_component(
hass,
template.DOMAIN,
config,
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
@pytest.fixture
async def setup_select(
hass: HomeAssistant,
count: int,
style: ConfigurationStyle,
select_config: dict[str, Any],
) -> None:
"""Do setup of select integration."""
if style == ConfigurationStyle.MODERN:
await async_setup_modern_format(
hass, count, {"name": _TEST_OBJECT_ID, **select_config}
)
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
@ -527,3 +567,36 @@ async def test_device_id(
template_entity = entity_registry.async_get("select.my_template")
assert template_entity is not None
assert template_entity.device_id == device_entry.id
@pytest.mark.parametrize(
("count", "select_config"),
[
(
1,
{
"state": "{{ 'b' }}",
"select_option": [],
"options": "{{ ['a', 'b'] }}",
"optimistic": True,
},
)
],
)
@pytest.mark.parametrize(
"style",
[
ConfigurationStyle.MODERN,
],
)
async def test_empty_action_config(hass: HomeAssistant, setup_select) -> None:
"""Test configuration with empty script."""
await hass.services.async_call(
select.DOMAIN,
select.SERVICE_SELECT_OPTION,
{ATTR_ENTITY_ID: _TEST_SELECT, "option": "a"},
blocking=True,
)
state = hass.states.get(_TEST_SELECT)
assert state.state == "a"

View File

@ -981,3 +981,49 @@ async def test_device_id(
template_entity = entity_registry.async_get("switch.my_template")
assert template_entity is not None
assert template_entity.device_id == device_entry.id
@pytest.mark.parametrize("count", [1])
@pytest.mark.parametrize(
("style", "switch_config"),
[
(
ConfigurationStyle.LEGACY,
{
TEST_OBJECT_ID: {
"turn_on": [],
"turn_off": [],
},
},
),
(
ConfigurationStyle.MODERN,
{
"name": TEST_OBJECT_ID,
"turn_on": [],
"turn_off": [],
},
),
],
)
async def test_empty_action_config(hass: HomeAssistant, setup_switch) -> None:
"""Test configuration with empty script."""
await hass.services.async_call(
switch.DOMAIN,
switch.SERVICE_TURN_ON,
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
blocking=True,
)
state = hass.states.get(TEST_ENTITY_ID)
assert state.state == STATE_ON
await hass.services.async_call(
switch.DOMAIN,
switch.SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: TEST_ENTITY_ID},
blocking=True,
)
state = hass.states.get(TEST_ENTITY_ID)
assert state.state == STATE_OFF

View File

@ -1,18 +1,29 @@
"""The tests for the Template vacuum platform."""
from typing import Any
import pytest
from homeassistant import setup
from homeassistant.components.vacuum import ATTR_BATTERY_LEVEL, VacuumActivity
from homeassistant.components import vacuum
from homeassistant.components.vacuum import (
ATTR_BATTERY_LEVEL,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_component import async_update_entity
from homeassistant.setup import async_setup_component
from .conftest import ConfigurationStyle
from tests.common import assert_setup_component
from tests.components.vacuum import common
_TEST_VACUUM = "vacuum.test_vacuum"
_TEST_OBJECT_ID = "test_vacuum"
_TEST_VACUUM = f"vacuum.{_TEST_OBJECT_ID}"
_STATE_INPUT_SELECT = "input_select.state"
_SPOT_CLEANING_INPUT_BOOLEAN = "input_boolean.spot_cleaning"
_LOCATING_INPUT_BOOLEAN = "input_boolean.locating"
@ -20,6 +31,50 @@ _FAN_SPEED_INPUT_SELECT = "input_select.fan_speed"
_BATTERY_LEVEL_INPUT_NUMBER = "input_number.battery_level"
async def async_setup_legacy_format(
hass: HomeAssistant, count: int, vacuum_config: dict[str, Any]
) -> None:
"""Do setup of number integration via new format."""
config = {"vacuum": {"platform": "template", "vacuums": vacuum_config}}
with assert_setup_component(count, vacuum.DOMAIN):
assert await async_setup_component(
hass,
vacuum.DOMAIN,
config,
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
@pytest.fixture
async def setup_vacuum(
hass: HomeAssistant,
count: int,
style: ConfigurationStyle,
vacuum_config: dict[str, Any],
) -> None:
"""Do setup of number integration."""
if style == ConfigurationStyle.LEGACY:
await async_setup_legacy_format(hass, count, vacuum_config)
@pytest.fixture
async def setup_test_vacuum_with_extra_config(
hass: HomeAssistant,
count: int,
style: ConfigurationStyle,
vacuum_config: dict[str, Any],
extra_config: dict[str, Any],
) -> None:
"""Do setup of number integration."""
config = {_TEST_OBJECT_ID: {**vacuum_config, **extra_config}}
if style == ConfigurationStyle.LEGACY:
await async_setup_legacy_format(hass, count, config)
@pytest.mark.parametrize(("count", "domain"), [(1, "vacuum")])
@pytest.mark.parametrize(
("parm1", "parm2", "config"),
@ -697,3 +752,71 @@ async def _register_components(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
@pytest.mark.parametrize("count", [1])
@pytest.mark.parametrize(
("style", "vacuum_config"),
[
(
ConfigurationStyle.LEGACY,
{
"start": [],
},
),
],
)
@pytest.mark.parametrize(
("extra_config", "supported_features"),
[
(
{
"pause": [],
},
VacuumEntityFeature.PAUSE,
),
(
{
"stop": [],
},
VacuumEntityFeature.STOP,
),
(
{
"return_to_base": [],
},
VacuumEntityFeature.RETURN_HOME,
),
(
{
"clean_spot": [],
},
VacuumEntityFeature.CLEAN_SPOT,
),
(
{
"locate": [],
},
VacuumEntityFeature.LOCATE,
),
(
{
"set_fan_speed": [],
},
VacuumEntityFeature.FAN_SPEED,
),
],
)
async def test_empty_action_config(
hass: HomeAssistant,
supported_features: VacuumEntityFeature,
setup_test_vacuum_with_extra_config,
) -> None:
"""Test configuration with empty script."""
await common.async_start(hass, _TEST_VACUUM)
await hass.async_block_till_done()
state = hass.states.get(_TEST_VACUUM)
assert state.attributes["supported_features"] == (
VacuumEntityFeature.STATE | VacuumEntityFeature.START | supported_features
)