Compare commits

...

13 Commits

Author SHA1 Message Date
Marc Mueller
00f42efc7e Update PyNaCl to 1.6.2 (#160909) 2026-01-14 18:21:09 +01:00
Erik Montnemery
9b9f94414b Add shared helper to assert conditions are hidden behind labs flag (#160941) 2026-01-14 16:53:17 +00:00
Erik Montnemery
f01653633d Add shared enable_experimental_triggers_conditions test fixture (#160937) 2026-01-14 16:01:06 +00:00
Erik Montnemery
1ace3e248f Add create_target_condition test helper (#160936) 2026-01-14 16:19:41 +01:00
epenet
d9bde85b58 Mark device_class type hints as compulsory in binary_sensor platform (#160934) 2026-01-14 16:18:04 +01:00
Joost Lekkerkerker
766a50abd7 Translate Hikvision NVR channel device name (#160862) 2026-01-14 16:16:26 +01:00
Niracler
9e6073099c Add button platform to sunricher_dali (#160908) 2026-01-14 16:02:25 +01:00
Erik Montnemery
892618d2ff Add fan conditions (#160832)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-01-14 15:50:22 +01:00
epenet
79c4164e03 Mark device_class type hints as compulsory in various platforms (#160929) 2026-01-14 15:47:17 +01:00
epenet
77dd4189b1 Mark device_class type hints as compulsory in sensor platform (#160931) 2026-01-14 15:46:40 +01:00
karwosts
4dbab23ada Duration selector for timer.change (#160645) 2026-01-14 15:45:32 +01:00
Erik Montnemery
ce7f1a6f6a Adjust docstring in entity registry (#160926) 2026-01-14 15:14:46 +01:00
Erik Montnemery
6fc28298aa Update matter test snapshots (#160924) 2026-01-14 14:53:02 +01:00
63 changed files with 895 additions and 539 deletions

View File

@@ -123,6 +123,7 @@ SERVICE_TRIGGER = "trigger"
NEW_TRIGGERS_CONDITIONS_FEATURE_FLAG = "new_triggers_conditions"
_EXPERIMENTAL_CONDITION_PLATFORMS = {
"fan",
"light",
}

View File

@@ -119,7 +119,7 @@ class Concord232ZoneSensor(BinarySensorEntity):
self._zone_type = zone_type
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass:
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type

View File

@@ -84,7 +84,7 @@ class DigitalOceanBinarySensor(BinarySensorEntity):
return self.data.status == "active"
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass:
"""Return the class of this sensor."""
return BinarySensorDeviceClass.MOVING

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
import datetime
import logging
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
@@ -96,7 +96,7 @@ class EbusdSensor(SensorEntity):
return None
@property
def device_class(self):
def device_class(self) -> SensorDeviceClass | None:
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class

View File

@@ -75,6 +75,6 @@ class EgardiaBinarySensor(BinarySensorEntity):
return self._state == STATE_ON
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass | None:
"""Return the device class."""
return self._device_class

View File

@@ -5,7 +5,10 @@ from __future__ import annotations
import datetime
import logging
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.const import ATTR_LAST_TRIP_TIME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -102,7 +105,7 @@ class EnvisalinkBinarySensor(EnvisalinkEntity, BinarySensorEntity):
return self._info["status"]["open"]
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass:
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type

View File

@@ -0,0 +1,17 @@
"""Provides conditions for fans."""
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.condition import Condition, make_entity_state_condition
from . import DOMAIN
CONDITIONS: dict[str, type[Condition]] = {
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
}
async def async_get_conditions(hass: HomeAssistant) -> dict[str, type[Condition]]:
"""Return the fan conditions."""
return CONDITIONS

View File

@@ -0,0 +1,17 @@
.condition_common: &condition_common
target:
entity:
domain: fan
fields:
behavior:
required: true
default: any
selector:
select:
translation_key: condition_behavior
options:
- all
- any
is_off: *condition_common
is_on: *condition_common

View File

@@ -1,4 +1,12 @@
{
"conditions": {
"is_off": {
"condition": "mdi:fan-off"
},
"is_on": {
"condition": "mdi:fan"
}
},
"entity_component": {
"_": {
"default": "mdi:fan",

View File

@@ -1,8 +1,32 @@
{
"common": {
"condition_behavior_description": "How the state should match on the targeted fans.",
"condition_behavior_name": "Behavior",
"trigger_behavior_description": "The behavior of the targeted fans to trigger on.",
"trigger_behavior_name": "Behavior"
},
"conditions": {
"is_off": {
"description": "Tests if one or more fans are off.",
"fields": {
"behavior": {
"description": "[%key:component::fan::common::condition_behavior_description%]",
"name": "[%key:component::fan::common::condition_behavior_name%]"
}
},
"name": "If a fan is off"
},
"is_on": {
"description": "Tests if one or more fans are on.",
"fields": {
"behavior": {
"description": "[%key:component::fan::common::condition_behavior_description%]",
"name": "[%key:component::fan::common::condition_behavior_name%]"
}
},
"name": "If a fan is on"
}
},
"device_automation": {
"action_type": {
"toggle": "[%key:common::device_automation::action_type::toggle%]",
@@ -65,6 +89,12 @@
}
},
"selector": {
"condition_behavior": {
"options": {
"all": "All",
"any": "Any"
}
},
"direction": {
"options": {
"forward": "Forward",

View File

@@ -57,7 +57,7 @@ class GeniusSwitch(GeniusZone, SwitchEntity):
"""Representation of a Genius Hub switch."""
@property
def device_class(self):
def device_class(self) -> SwitchDeviceClass:
"""Return the class of this device, from component DEVICE_CLASSES."""
return SwitchDeviceClass.OUTLET

View File

@@ -191,7 +191,11 @@ class HikvisionBinarySensor(BinarySensorEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{self._data.device_id}_{channel}")},
via_device=(DOMAIN, self._data.device_id),
name=f"{self._data.device_name} Channel {channel}",
translation_key="nvr_channel",
translation_placeholders={
"device_name": self._data.device_name,
"channel_number": str(channel),
},
manufacturer="Hikvision",
model="NVR Channel",
)

View File

@@ -62,7 +62,11 @@ class HikvisionCamera(Camera):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{self._data.device_id}_{channel}")},
via_device=(DOMAIN, self._data.device_id),
name=f"{self._data.device_name} Channel {channel}",
translation_key="nvr_channel",
translation_placeholders={
"device_name": self._data.device_name,
"channel_number": str(channel),
},
manufacturer="Hikvision",
model="NVR Channel",
)

View File

@@ -29,6 +29,11 @@
}
}
},
"device": {
"nvr_channel": {
"name": "{device_name} channel {channel_number}"
}
},
"issues": {
"deprecated_yaml_import_issue": {
"description": "Configuring {integration_title} using YAML is deprecated and the import failed. Please remove the `{domain}` entry from your `configuration.yaml` file and set up the integration manually.",

View File

@@ -66,7 +66,7 @@ class HMBinarySensor(HMDevice, BinarySensorEntity):
return bool(self._hm_get_state())
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass | None:
"""Return the class of this sensor from DEVICE_CLASSES."""
# If state is MOTION (Only RemoteMotion working)
if self._state == "MOTION":

View File

@@ -1,126 +1,14 @@
"""Provides conditions for lights."""
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Final, Unpack, override
import voluptuous as vol
from homeassistant.const import CONF_OPTIONS, CONF_TARGET, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, split_entity_id
from homeassistant.helpers import config_validation as cv, target
from homeassistant.helpers.condition import (
Condition,
ConditionChecker,
ConditionCheckParams,
ConditionConfig,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.condition import Condition, make_entity_state_condition
from .const import DOMAIN
ATTR_BEHAVIOR: Final = "behavior"
BEHAVIOR_ANY: Final = "any"
BEHAVIOR_ALL: Final = "all"
STATE_CONDITION_VALID_STATES: Final = [STATE_ON, STATE_OFF]
STATE_CONDITION_OPTIONS_SCHEMA: dict[vol.Marker, Any] = {
vol.Required(ATTR_BEHAVIOR, default=BEHAVIOR_ANY): vol.In(
[BEHAVIOR_ANY, BEHAVIOR_ALL]
),
}
STATE_CONDITION_SCHEMA = vol.Schema(
{
vol.Required(CONF_TARGET): cv.TARGET_FIELDS,
vol.Required(CONF_OPTIONS): STATE_CONDITION_OPTIONS_SCHEMA,
}
)
class StateConditionBase(Condition):
"""State condition."""
@override
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
return STATE_CONDITION_SCHEMA(config) # type: ignore[no-any-return]
def __init__(
self, hass: HomeAssistant, config: ConditionConfig, state: str
) -> None:
"""Initialize condition."""
super().__init__(hass, config)
if TYPE_CHECKING:
assert config.target
assert config.options
self._target_selection = target.TargetSelection(config.target)
self._behavior = config.options[ATTR_BEHAVIOR]
self._state = state
@override
async def async_get_checker(self) -> ConditionChecker:
"""Get the condition checker."""
def check_any_match_state(states: list[str]) -> bool:
"""Test if any entity match the state."""
return any(state == self._state for state in states)
def check_all_match_state(states: list[str]) -> bool:
"""Test if all entities match the state."""
return all(state == self._state for state in states)
matcher: Callable[[list[str]], bool]
if self._behavior == BEHAVIOR_ANY:
matcher = check_any_match_state
elif self._behavior == BEHAVIOR_ALL:
matcher = check_all_match_state
def test_state(**kwargs: Unpack[ConditionCheckParams]) -> bool:
"""Test state condition."""
targeted_entities = target.async_extract_referenced_entity_ids(
self._hass, self._target_selection, expand_group=False
)
referenced_entity_ids = targeted_entities.referenced.union(
targeted_entities.indirectly_referenced
)
light_entity_ids = {
entity_id
for entity_id in referenced_entity_ids
if split_entity_id(entity_id)[0] == DOMAIN
}
light_entity_states = [
state.state
for entity_id in light_entity_ids
if (state := self._hass.states.get(entity_id))
and state.state in STATE_CONDITION_VALID_STATES
]
return matcher(light_entity_states)
return test_state
class IsOnCondition(StateConditionBase):
"""Is on condition."""
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
"""Initialize condition."""
super().__init__(hass, config, STATE_ON)
class IsOffCondition(StateConditionBase):
"""Is off condition."""
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
"""Initialize condition."""
super().__init__(hass, config, STATE_OFF)
CONDITIONS: dict[str, type[Condition]] = {
"is_off": IsOffCondition,
"is_on": IsOnCondition,
"is_off": make_entity_state_condition(DOMAIN, STATE_OFF),
"is_on": make_entity_state_condition(DOMAIN, STATE_ON),
}

View File

@@ -116,7 +116,7 @@ class MfiSensor(SensorEntity):
return round(self._port.value, digits)
@property
def device_class(self):
def device_class(self) -> SensorDeviceClass | None:
"""Return the device class of the sensor."""
try:
tag = self._port.tag

View File

@@ -16,5 +16,5 @@
"iot_class": "local_push",
"loggers": ["nacl"],
"quality_scale": "internal",
"requirements": ["PyNaCl==1.6.0"]
"requirements": ["PyNaCl==1.6.2"]
}

View File

@@ -2,7 +2,10 @@
from __future__ import annotations
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -75,7 +78,7 @@ class NessZoneBinarySensor(BinarySensorEntity):
return self._state == 1
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass:
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._type

View File

@@ -96,7 +96,7 @@ class NX584ZoneSensor(BinarySensorEntity):
self._zone_type = zone_type
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass:
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._zone_type

View File

@@ -84,7 +84,7 @@ class OASATelematicsSensor(SensorEntity):
return self._name
@property
def device_class(self):
def device_class(self) -> SensorDeviceClass:
"""Return the class of this sensor."""
return SensorDeviceClass.TIMESTAMP

View File

@@ -9,6 +9,6 @@
"integration_type": "service",
"iot_class": "local_push",
"loggers": ["nacl"],
"requirements": ["PyNaCl==1.6.0"],
"requirements": ["PyNaCl==1.6.2"],
"single_config_entry": true
}

View File

@@ -6,7 +6,10 @@ import logging
from pyqwikswitch.qwikswitch import SENSORS
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
@@ -76,6 +79,6 @@ class QSBinarySensor(QSEntity, BinarySensorEntity):
return f"qs{self.qsid}:{self.channel}"
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass:
"""Return the class of this sensor."""
return self._class

View File

@@ -25,7 +25,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import CONF_SERIAL_NUMBER, DOMAIN, MANUFACTURER
from .types import DaliCenterConfigEntry, DaliCenterData
_PLATFORMS: list[Platform] = [Platform.LIGHT, Platform.SCENE]
_PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.LIGHT, Platform.SCENE]
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,63 @@
"""Support for Sunricher DALI device identify button."""
from __future__ import annotations
import logging
from PySrDaliGateway import Device
from PySrDaliGateway.helper import is_light_device
from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN, MANUFACTURER
from .entity import DaliDeviceEntity
from .types import DaliCenterConfigEntry
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
entry: DaliCenterConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Sunricher DALI button entities from config entry."""
devices = entry.runtime_data.devices
async_add_entities(
DaliCenterIdentifyButton(device)
for device in devices
if is_light_device(device.dev_type)
)
class DaliCenterIdentifyButton(DaliDeviceEntity, ButtonEntity):
"""Representation of a Sunricher DALI device identify button."""
_attr_device_class = ButtonDeviceClass.IDENTIFY
_attr_entity_category = EntityCategory.CONFIG
_attr_name = None
def __init__(self, device: Device) -> None:
"""Initialize the device identify button."""
super().__init__(device)
self._device = device
self._attr_unique_id = f"{device.unique_id}_identify"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.dev_id)},
name=device.name,
manufacturer=MANUFACTURER,
model=device.model,
via_device=(DOMAIN, device.gw_sn),
)
async def async_press(self) -> None:
"""Handle button press to identify device."""
_LOGGER.debug("Identifying device %s", self._device.dev_id)
self._device.identify()

View File

@@ -35,6 +35,7 @@ change:
required: true
example: "00:01:00, 60 or -60"
selector:
text:
duration:
allow_negative: true
reload:

View File

@@ -688,7 +688,7 @@ class UtilityMeterSensor(RestoreSensor):
self._collecting = None
@property
def device_class(self):
def device_class(self) -> SensorDeviceClass | None:
"""Return the device class of the sensor."""
if self._input_device_class is not None:
return self._input_device_class

View File

@@ -10,6 +10,7 @@ import W800rf32 as w800
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA,
PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_DEVICES, CONF_NAME
@@ -98,7 +99,7 @@ class W800rf32BinarySensor(BinarySensorEntity):
return self._name
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass | None:
"""Return the sensor class."""
return self._device_class

View File

@@ -6,6 +6,7 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.const import CONF_MONITORED_CONDITIONS, STATE_OFF, STATE_ON, Platform
@@ -124,7 +125,7 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorEntity):
return self._state == STATE_ON
@property
def device_class(self):
def device_class(self) -> BinarySensorDeviceClass:
"""Return the class of the binary sensor."""
return self._sensor_type

View File

@@ -13,7 +13,17 @@ import inspect
import logging
import re
import sys
from typing import TYPE_CHECKING, Any, Protocol, TypedDict, Unpack, cast, overload
from typing import (
TYPE_CHECKING,
Any,
Final,
Protocol,
TypedDict,
Unpack,
cast,
overload,
override,
)
import voluptuous as vol
@@ -43,7 +53,7 @@ from homeassistant.const import (
STATE_UNKNOWN,
WEEKDAYS,
)
from homeassistant.core import HomeAssistant, State, callback
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
from homeassistant.exceptions import (
ConditionError,
ConditionErrorContainer,
@@ -71,6 +81,7 @@ from .automation import (
)
from .integration_platform import async_process_integration_platforms
from .selector import TargetSelector
from .target import TargetSelection, async_extract_referenced_entity_ids
from .template import Template, render_complex
from .trace import (
TraceElement,
@@ -302,6 +313,112 @@ class Condition(abc.ABC):
"""Get the condition checker."""
ATTR_BEHAVIOR: Final = "behavior"
BEHAVIOR_ANY: Final = "any"
BEHAVIOR_ALL: Final = "all"
STATE_CONDITION_OPTIONS_SCHEMA: dict[vol.Marker, Any] = {
vol.Required(ATTR_BEHAVIOR, default=BEHAVIOR_ANY): vol.In(
[BEHAVIOR_ANY, BEHAVIOR_ALL]
),
}
ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL = vol.Schema(
{
vol.Required(CONF_TARGET): cv.TARGET_FIELDS,
vol.Required(CONF_OPTIONS): STATE_CONDITION_OPTIONS_SCHEMA,
}
)
class EntityStateConditionBase(Condition):
"""State condition."""
_domain: str
_schema: vol.Schema = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL
_states: set[str]
@override
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
return cast(ConfigType, cls._schema(config))
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
"""Initialize condition."""
super().__init__(hass, config)
if TYPE_CHECKING:
assert config.target
assert config.options
self._target_selection = TargetSelection(config.target)
self._behavior = config.options[ATTR_BEHAVIOR]
def entity_filter(self, entities: set[str]) -> set[str]:
"""Filter entities of this domain."""
return {
entity_id
for entity_id in entities
if split_entity_id(entity_id)[0] == self._domain
}
@override
async def async_get_checker(self) -> ConditionChecker:
"""Get the condition checker."""
def check_any_match_state(states: list[str]) -> bool:
"""Test if any entity match the state."""
return any(state in self._states for state in states)
def check_all_match_state(states: list[str]) -> bool:
"""Test if all entities match the state."""
return all(state in self._states for state in states)
matcher: Callable[[list[str]], bool]
if self._behavior == BEHAVIOR_ANY:
matcher = check_any_match_state
elif self._behavior == BEHAVIOR_ALL:
matcher = check_all_match_state
def test_state(**kwargs: Unpack[ConditionCheckParams]) -> bool:
"""Test state condition."""
targeted_entities = async_extract_referenced_entity_ids(
self._hass, self._target_selection, expand_group=False
)
referenced_entity_ids = targeted_entities.referenced.union(
targeted_entities.indirectly_referenced
)
filtered_entity_ids = self.entity_filter(referenced_entity_ids)
entity_states = [
_state.state
for entity_id in filtered_entity_ids
if (_state := self._hass.states.get(entity_id))
and _state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
]
return matcher(entity_states)
return test_state
def make_entity_state_condition(
domain: str, states: str | set[str]
) -> type[EntityStateConditionBase]:
"""Create a condition for entity state changes to specific state(s)."""
if isinstance(states, str):
states_set = {states}
else:
states_set = states
class CustomCondition(EntityStateConditionBase):
"""Condition for entity state."""
_domain = domain
_states = states_set
return CustomCondition
class ConditionProtocol(Protocol):
"""Define the format of condition modules."""

View File

@@ -987,10 +987,12 @@ class EntityRegistry(BaseRegistry):
) -> str:
"""Generate an entity ID, based on all the provided parameters.
`name` is the name set by the user, not the original name from the integration.
`name` has priority over `suggested_object_id`, which has priority
over `object_id_base`.
`name` and `suggested_object_id` do not cause the use of device name,
`object_id_base` does if `has_entity_name` is True.
`name` and `suggested_object_id` will never be prefixed with the device name,
`object_id_base` will be if `has_entity_name` is True.
Entity ID conflicts are checked against registered and currently
existing entities, as well as provided `reserved_entity_ids`.
"""

View File

@@ -54,7 +54,7 @@ propcache==0.4.1
psutil-home-assistant==0.0.1
PyJWT==2.10.1
pymicro-vad==1.0.1
PyNaCl==1.6.0
PyNaCl==1.6.2
pyOpenSSL==25.3.0
pyserial==3.5
pyspeex-noise==1.0.2

View File

@@ -931,6 +931,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["BinarySensorDeviceClass", None],
mandatory=True,
),
TypeHintMatch(
function_name="is_on",
@@ -954,6 +955,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["ButtonDeviceClass", None],
mandatory=True,
),
TypeHintMatch(
function_name="press",
@@ -1366,6 +1368,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["CoverDeviceClass", None],
mandatory=True,
),
TypeHintMatch(
function_name="current_cover_position",
@@ -1991,6 +1994,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["MediaPlayerDeviceClass", None],
mandatory=True,
),
TypeHintMatch(
function_name="state",
@@ -2334,6 +2338,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["NumberDeviceClass", None],
mandatory=True,
),
TypeHintMatch(
function_name="capability_attributes",
@@ -2508,6 +2513,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["SensorDeviceClass", None],
mandatory=True,
),
TypeHintMatch(
function_name="state_class",
@@ -2632,6 +2638,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["SwitchDeviceClass", None],
mandatory=True,
),
],
),
@@ -2736,6 +2743,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
TypeHintMatch(
function_name="device_class",
return_type=["UpdateDeviceClass", None],
mandatory=True,
),
TypeHintMatch(
function_name="in_progress",

2
requirements_all.txt generated
View File

@@ -70,7 +70,7 @@ PyMicroBot==0.0.23
# homeassistant.components.mobile_app
# homeassistant.components.owntracks
PyNaCl==1.6.0
PyNaCl==1.6.2
# homeassistant.auth.mfa_modules.totp
# homeassistant.components.homekit

View File

@@ -70,7 +70,7 @@ PyMicroBot==0.0.23
# homeassistant.components.mobile_app
# homeassistant.components.owntracks
PyNaCl==1.6.0
PyNaCl==1.6.2
# homeassistant.auth.mfa_modules.totp
# homeassistant.components.homekit

View File

@@ -5,6 +5,8 @@ from enum import StrEnum
import itertools
from typing import Any, TypedDict
import pytest
from homeassistant.const import (
ATTR_AREA_ID,
ATTR_DEVICE_ID,
@@ -12,6 +14,7 @@ from homeassistant.const import (
ATTR_LABEL_ID,
CONF_ABOVE,
CONF_BELOW,
CONF_CONDITION,
CONF_ENTITY_ID,
CONF_OPTIONS,
CONF_PLATFORM,
@@ -27,6 +30,10 @@ from homeassistant.helpers import (
floor_registry as fr,
label_registry as lr,
)
from homeassistant.helpers.condition import (
ConditionCheckerTypeOptional,
async_from_config as async_condition_from_config,
)
from homeassistant.helpers.trigger import (
CONF_LOWER_LIMIT,
CONF_THRESHOLD_TYPE,
@@ -585,6 +592,24 @@ async def arm_trigger(
)
async def create_target_condition(
hass: HomeAssistant,
*,
condition: str,
target: dict,
behavior: str,
) -> ConditionCheckerTypeOptional:
"""Create a target condition."""
return await async_condition_from_config(
hass,
{
CONF_CONDITION: condition,
CONF_TARGET: target,
CONF_OPTIONS: {"behavior": behavior},
},
)
def set_or_remove_state(
hass: HomeAssistant,
entity_id: str,
@@ -611,3 +636,37 @@ def other_states(state: StrEnum | Iterable[StrEnum]) -> list[str]:
enum_class = list(state)[0].__class__
return sorted({s.value for s in enum_class} - excluded_values)
async def assert_condition_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, condition: str
) -> None:
"""Helper to check that a condition is gated by the labs flag."""
# Local include to avoid importing the automation component unnecessarily
from homeassistant.components import automation # noqa: PLC0415
await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
CONF_CONDITION: condition,
CONF_TARGET: {ATTR_LABEL_ID: "test_label"},
CONF_OPTIONS: {"behavior": "any"},
},
"action": {
"service": "test.automation",
},
}
},
)
assert (
"Unnamed automation failed to setup conditions and has been disabled: "
f"Condition '{condition}' requires the experimental 'New triggers and "
"conditions' feature to be enabled in Home Assistant Labs settings "
"(feature flag: 'new_triggers_conditions')"
) in caplog.text

View File

@@ -1,8 +1,6 @@
"""Test alarm control panel triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -29,16 +27,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_alarm_control_panels(hass: HomeAssistant) -> list[str]:
"""Create multiple alarm control panel entities associated with different targets."""
@@ -70,7 +58,7 @@ async def test_alarm_control_panel_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("alarm_control_panel"),
@@ -181,7 +169,7 @@ async def test_alarm_control_panel_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("alarm_control_panel"),
@@ -291,7 +279,7 @@ async def test_alarm_control_panel_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("alarm_control_panel"),

View File

@@ -1,8 +1,6 @@
"""Test assist satellite triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -26,16 +24,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_assist_satellites(hass: HomeAssistant) -> list[str]:
"""Create multiple assist satellite entities associated with different targets."""
@@ -64,7 +52,7 @@ async def test_assist_satellite_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("assist_satellite"),
@@ -132,7 +120,7 @@ async def test_assist_satellite_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("assist_satellite"),
@@ -199,7 +187,7 @@ async def test_assist_satellite_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("assist_satellite"),

View File

@@ -1,8 +1,6 @@
"""Test binary sensor trigger."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -30,16 +28,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_binary_sensors(hass: HomeAssistant) -> tuple[list[str], list[str]]:
"""Create multiple binary sensor entities associated with different targets."""
@@ -66,7 +54,7 @@ async def test_binary_sensor_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("binary_sensor"),
@@ -136,7 +124,7 @@ async def test_binary_sensor_state_attribute_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("binary_sensor"),
@@ -205,7 +193,7 @@ async def test_binary_sensor_state_attribute_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("binary_sensor"),

View File

@@ -1,8 +1,5 @@
"""Test button trigger."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from homeassistant.const import (
@@ -27,16 +24,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_buttons(hass: HomeAssistant) -> list[str]:
"""Create multiple button entities associated with different targets."""
@@ -57,7 +44,7 @@ async def test_button_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("button"),

View File

@@ -1,9 +1,7 @@
"""Test climate trigger."""
from collections.abc import Generator
from contextlib import AbstractContextManager, nullcontext as does_not_raise
from typing import Any
from unittest.mock import patch
import pytest
import voluptuous as vol
@@ -45,16 +43,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_climates(hass: HomeAssistant) -> list[str]:
"""Create multiple climate entities associated with different targets."""
@@ -91,7 +79,7 @@ async def test_climate_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger", "trigger_options", "expected_result"),
[
@@ -147,7 +135,7 @@ async def test_climate_trigger_validation(
)
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
@@ -220,7 +208,7 @@ async def test_climate_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
@@ -315,7 +303,7 @@ async def test_climate_state_attribute_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
@@ -389,7 +377,7 @@ async def test_climate_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
@@ -471,7 +459,7 @@ async def test_climate_state_attribute_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),
@@ -544,7 +532,7 @@ async def test_climate_state_trigger_behavior_last(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("climate"),

View File

@@ -1131,3 +1131,13 @@ async def check_translations(
for description in translation_errors.values():
if description != "used":
pytest.fail(description)
@pytest.fixture(name="enable_labs_preview_features")
def enable_labs_preview_features() -> Generator[None]:
"""Enable labs preview features."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield

View File

@@ -1,8 +1,6 @@
"""Test device_tracker trigger."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -31,16 +29,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_device_trackers(hass: HomeAssistant) -> list[str]:
"""Create multiple device_trackers entities associated with different targets."""
@@ -64,7 +52,7 @@ async def test_device_tracker_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("device_tracker"),
@@ -122,7 +110,7 @@ async def test_device_tracker_home_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("device_tracker"),
@@ -179,7 +167,7 @@ async def test_device_tracker_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("device_tracker"),

View File

@@ -0,0 +1,185 @@
"""Test fan conditions."""
from typing import Any
import pytest
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from tests.components import (
ConditionStateDescription,
assert_condition_gated_by_labs_flag,
create_target_condition,
parametrize_condition_states,
parametrize_target_entities,
set_or_remove_state,
target_entities,
)
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture
async def target_fans(hass: HomeAssistant) -> list[str]:
"""Create multiple fan entities associated with different targets."""
return (await target_entities(hass, "fan"))["included"]
@pytest.fixture
async def target_switches(hass: HomeAssistant) -> list[str]:
"""Create multiple switch entities associated with different targets."""
return (await target_entities(hass, "switch"))["included"]
@pytest.mark.parametrize(
"condition",
[
"fan.is_off",
"fan.is_on",
],
)
async def test_fan_conditions_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, condition: str
) -> None:
"""Test the fan conditions are gated by the labs flag."""
await assert_condition_gated_by_labs_flag(hass, caplog, condition)
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("fan"),
)
@pytest.mark.parametrize(
("condition", "condition_options", "states"),
[
*parametrize_condition_states(
condition="fan.is_on",
target_states=[STATE_ON],
other_states=[STATE_OFF],
),
*parametrize_condition_states(
condition="fan.is_off",
target_states=[STATE_OFF],
other_states=[STATE_ON],
),
],
)
async def test_fan_state_condition_behavior_any(
hass: HomeAssistant,
target_fans: list[str],
target_switches: list[str],
condition_target_config: dict,
entity_id: str,
entities_in_target: int,
condition: str,
condition_options: dict[str, Any],
states: list[ConditionStateDescription],
) -> None:
"""Test the fan state condition with the 'any' behavior."""
other_entity_ids = set(target_fans) - {entity_id}
# Set all fans, including the tested fan, to the initial state
for eid in target_fans:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,
behavior="any",
)
# Set state for switches to ensure that they don't impact the condition
for state in states:
for eid in target_switches:
set_or_remove_state(hass, eid, state["included"])
await hass.async_block_till_done()
assert condition(hass) is False
for state in states:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
assert condition(hass) == state["condition_true"]
# Check if changing other fans also passes the condition
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
assert condition(hass) == state["condition_true"]
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("fan"),
)
@pytest.mark.parametrize(
("condition", "condition_options", "states"),
[
*parametrize_condition_states(
condition="fan.is_on",
target_states=[STATE_ON],
other_states=[STATE_OFF],
),
*parametrize_condition_states(
condition="fan.is_off",
target_states=[STATE_OFF],
other_states=[STATE_ON],
),
],
)
async def test_fan_state_condition_behavior_all(
hass: HomeAssistant,
target_fans: list[str],
condition_target_config: dict,
entity_id: str,
entities_in_target: int,
condition: str,
condition_options: dict[str, Any],
states: list[ConditionStateDescription],
) -> None:
"""Test the fan state condition with the 'all' behavior."""
# Set state for two switches to ensure that they don't impact the condition
hass.states.async_set("switch.label_switch_1", STATE_OFF)
hass.states.async_set("switch.label_switch_2", STATE_ON)
other_entity_ids = set(target_fans) - {entity_id}
# Set all fans, including the tested fan, to the initial state
for eid in target_fans:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,
behavior="all",
)
for state in states:
included_state = state["included"]
set_or_remove_state(hass, entity_id, included_state)
await hass.async_block_till_done()
# The condition passes if all entities are either in a target state or invalid
assert condition(hass) == (
(not state["state_valid"])
or (state["condition_true"] and entities_in_target == 1)
)
for other_entity_id in other_entity_ids:
set_or_remove_state(hass, other_entity_id, included_state)
await hass.async_block_till_done()
# The condition passes if all entities are either in a target state or invalid
assert condition(hass) == (
(not state["state_valid"]) or state["condition_true"]
)

View File

@@ -1,8 +1,6 @@
"""Test fan trigger."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -24,16 +22,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_fans(hass: HomeAssistant) -> list[str]:
"""Create multiple fan entities associated with different targets."""
@@ -60,7 +48,7 @@ async def test_fan_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("fan"),
@@ -118,7 +106,7 @@ async def test_fan_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("fan"),
@@ -175,7 +163,7 @@ async def test_fan_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("fan"),

View File

@@ -92,7 +92,7 @@
'attributes': ReadOnlyDict({
'access_token': '1caab5c3b3',
'entity_picture': '/api/camera_proxy/camera.front_camera_channel_1?token=1caab5c3b3',
'friendly_name': 'Front Camera Channel 1',
'friendly_name': 'Front Camera channel 1',
'supported_features': <CameraEntityFeature: 2>,
}),
'context': <ANY>,
@@ -144,7 +144,7 @@
'attributes': ReadOnlyDict({
'access_token': '1caab5c3b3',
'entity_picture': '/api/camera_proxy/camera.front_camera_channel_2?token=1caab5c3b3',
'friendly_name': 'Front Camera Channel 2',
'friendly_name': 'Front Camera channel 2',
'supported_features': <CameraEntityFeature: 2>,
}),
'context': <ANY>,

View File

@@ -1,8 +1,6 @@
"""Test humidifier trigger."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -31,16 +29,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_humidifiers(hass: HomeAssistant) -> list[str]:
"""Create multiple humidifier entities associated with different targets."""
@@ -71,7 +59,7 @@ async def test_humidifier_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("humidifier"),
@@ -129,7 +117,7 @@ async def test_humidifier_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("humidifier"),
@@ -195,7 +183,7 @@ async def test_humidifier_state_attribute_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("humidifier"),
@@ -252,7 +240,7 @@ async def test_humidifier_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("humidifier"),
@@ -316,7 +304,7 @@ async def test_humidifier_state_attribute_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("humidifier"),
@@ -372,7 +360,7 @@ async def test_humidifier_state_trigger_behavior_last(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("humidifier"),

View File

@@ -1,8 +1,6 @@
"""Test lawn mower triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -26,16 +24,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_lawn_mowers(hass: HomeAssistant) -> list[str]:
"""Create multiple lawn mower entities associated with different targets."""
@@ -64,7 +52,7 @@ async def test_lawn_mower_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("lawn_mower"),
@@ -132,7 +120,7 @@ async def test_lawn_mower_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("lawn_mower"),
@@ -199,7 +187,7 @@ async def test_lawn_mower_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("lawn_mower"),

View File

@@ -1,29 +1,16 @@
"""Test light conditions."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
from homeassistant.components import automation
from homeassistant.const import (
ATTR_LABEL_ID,
CONF_CONDITION,
CONF_OPTIONS,
CONF_TARGET,
STATE_OFF,
STATE_ON,
)
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.condition import (
ConditionCheckerTypeOptional,
async_from_config,
)
from homeassistant.setup import async_setup_component
from tests.components import (
ConditionStateDescription,
assert_condition_gated_by_labs_flag,
create_target_condition,
parametrize_condition_states,
parametrize_target_entities,
set_or_remove_state,
@@ -48,61 +35,6 @@ async def target_switches(hass: HomeAssistant) -> list[str]:
return (await target_entities(hass, "switch"))["included"]
async def setup_automation_with_light_condition(
hass: HomeAssistant,
*,
condition: str,
target: dict,
behavior: str,
) -> None:
"""Set up automation with light state condition."""
await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {"platform": "event", "event_type": "test_event"},
"condition": {
CONF_CONDITION: condition,
CONF_TARGET: target,
CONF_OPTIONS: {"behavior": behavior},
},
"action": {
"service": "test.automation",
},
}
},
)
async def create_condition(
hass: HomeAssistant,
*,
condition: str,
target: dict,
behavior: str,
) -> ConditionCheckerTypeOptional:
"""Create a light state condition."""
return await async_from_config(
hass,
{
CONF_CONDITION: condition,
CONF_TARGET: target,
CONF_OPTIONS: {"behavior": behavior},
},
)
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.mark.parametrize(
"condition",
[
@@ -114,18 +46,10 @@ async def test_light_conditions_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, condition: str
) -> None:
"""Test the light conditions are gated by the labs flag."""
await setup_automation_with_light_condition(
hass, condition=condition, target={ATTR_LABEL_ID: "test_label"}, behavior="any"
)
assert (
"Unnamed automation failed to setup conditions and has been disabled: "
f"Condition '{condition}' requires the experimental 'New triggers and "
"conditions' feature to be enabled in Home Assistant Labs settings "
"(feature flag: 'new_triggers_conditions')"
) in caplog.text
await assert_condition_gated_by_labs_flag(hass, caplog, condition)
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),
@@ -164,7 +88,7 @@ async def test_light_state_condition_behavior_any(
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_condition(
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,
@@ -191,7 +115,7 @@ async def test_light_state_condition_behavior_any(
assert condition(hass) == state["condition_true"]
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("condition_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),
@@ -234,7 +158,7 @@ async def test_light_state_condition_behavior_all(
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
condition = await create_condition(
condition = await create_target_condition(
hass,
condition=condition,
target=condition_target_config,

View File

@@ -1,8 +1,6 @@
"""Test light trigger."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -27,16 +25,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_lights(hass: HomeAssistant) -> list[str]:
"""Create multiple light entities associated with different targets."""
@@ -65,7 +53,7 @@ async def test_light_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),
@@ -123,7 +111,7 @@ async def test_light_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),
@@ -177,7 +165,7 @@ async def test_light_state_attribute_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),
@@ -234,7 +222,7 @@ async def test_light_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),
@@ -286,7 +274,7 @@ async def test_light_state_attribute_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),
@@ -342,7 +330,7 @@ async def test_light_state_trigger_behavior_last(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("light"),

View File

@@ -1,8 +1,6 @@
"""Test lock triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -26,16 +24,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_locks(hass: HomeAssistant) -> list[str]:
"""Create multiple lock entities associated with different targets."""
@@ -64,7 +52,7 @@ async def test_lock_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -132,7 +120,7 @@ async def test_lock_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -199,7 +187,7 @@ async def test_lock_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),

View File

@@ -143,6 +143,7 @@
'labels': set({
}),
'name': None,
'object_id_base': 'Occupied setback',
'options': dict({
}),
'original_device_class': None,

View File

@@ -1,8 +1,6 @@
"""Test media player trigger."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -25,16 +23,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_media_players(hass: HomeAssistant) -> list[str]:
"""Create multiple media player entities associated with different targets."""
@@ -60,7 +48,7 @@ async def test_media_player_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("media_player"),
@@ -121,7 +109,7 @@ async def test_media_player_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("media_player"),
@@ -181,7 +169,7 @@ async def test_media_player_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("media_player"),

View File

@@ -1,8 +1,6 @@
"""Test person trigger."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -32,16 +30,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_persons(hass: HomeAssistant) -> list[str]:
"""Create multiple persons entities associated with different targets."""
@@ -65,7 +53,7 @@ async def test_person_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -123,7 +111,7 @@ async def test_person_home_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -180,7 +168,7 @@ async def test_person_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),

View File

@@ -1,8 +1,5 @@
"""Test scene trigger."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from homeassistant.const import (
@@ -27,16 +24,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_scenes(hass: HomeAssistant) -> list[str]:
"""Create multiple scene entities associated with different targets."""
@@ -57,7 +44,7 @@ async def test_scene_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("scene"),

View File

@@ -1,8 +1,6 @@
"""Test siren triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -25,16 +23,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_sirens(hass: HomeAssistant) -> list[str]:
"""Create multiple siren entities associated with different targets."""
@@ -61,7 +49,7 @@ async def test_siren_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -119,7 +107,7 @@ async def test_siren_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -176,7 +164,7 @@ async def test_siren_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),

View File

@@ -0,0 +1,197 @@
# serializer version: 1
# name: test_entities[button.cct_0000_03-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.cct_0000_03',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': None,
'platform': 'sunricher_dali',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '01020000036A242121110E_identify',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.cct_0000_03-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'CCT 0000-03',
}),
'context': <ANY>,
'entity_id': 'button.cct_0000_03',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.dimmer_0000_02-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.dimmer_0000_02',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': None,
'platform': 'sunricher_dali',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '01010000026A242121110E_identify',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.dimmer_0000_02-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'Dimmer 0000-02',
}),
'context': <ANY>,
'entity_id': 'button.dimmer_0000_02',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.hs_color_light-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.hs_color_light',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': None,
'platform': 'sunricher_dali',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '01030000046A242121110E_identify',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.hs_color_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'HS Color Light',
}),
'context': <ANY>,
'entity_id': 'button.hs_color_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.rgbw_light-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.rgbw_light',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
'original_icon': None,
'original_name': None,
'platform': 'sunricher_dali',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '01040000056A242121110E_identify',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.rgbw_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'identify',
'friendly_name': 'RGBW Light',
}),
'context': <ANY>,
'entity_id': 'button.rgbw_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@@ -0,0 +1,46 @@
"""Test the Sunricher DALI button platform."""
from unittest.mock import MagicMock
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@pytest.fixture
def platforms() -> list[Platform]:
"""Fixture to specify which platforms to test."""
return [Platform.BUTTON]
@pytest.mark.usefixtures("init_integration")
async def test_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the button entities."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("init_integration")
async def test_identify_button_press(
hass: HomeAssistant,
mock_devices: list[MagicMock],
) -> None:
"""Test pressing the identify button calls device.identify()."""
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.dimmer_0000_02"},
blocking=True,
)
mock_devices[0].identify.assert_called_once()

View File

@@ -1,8 +1,6 @@
"""Test switch triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -25,16 +23,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_switches(hass: HomeAssistant) -> list[str]:
"""Create multiple switch entities associated with different targets."""
@@ -61,7 +49,7 @@ async def test_switch_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -119,7 +107,7 @@ async def test_switch_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -176,7 +164,7 @@ async def test_switch_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),

View File

@@ -1,8 +1,5 @@
"""Test text trigger."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from homeassistant.const import (
@@ -27,16 +24,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_texts(hass: HomeAssistant) -> list[str]:
"""Create multiple text entities associated with different targets."""
@@ -57,7 +44,7 @@ async def test_text_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("text"),

View File

@@ -1,8 +1,6 @@
"""Test update triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -25,16 +23,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_updates(hass: HomeAssistant) -> list[str]:
"""Create multiple update entities associated with different targets."""
@@ -60,7 +48,7 @@ async def test_update_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -113,7 +101,7 @@ async def test_update_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),
@@ -165,7 +153,7 @@ async def test_update_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities(DOMAIN),

View File

@@ -1,8 +1,6 @@
"""Test vacuum triggers."""
from collections.abc import Generator
from typing import Any
from unittest.mock import patch
import pytest
@@ -26,16 +24,6 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
"""Stub copying the blueprints to the config folder."""
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
async def target_vacuums(hass: HomeAssistant) -> list[str]:
"""Create multiple vacuum entities associated with different targets."""
@@ -64,7 +52,7 @@ async def test_vacuum_triggers_gated_by_labs_flag(
) in caplog.text
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("vacuum"),
@@ -132,7 +120,7 @@ async def test_vacuum_state_trigger_behavior_any(
service_calls.clear()
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("vacuum"),
@@ -199,7 +187,7 @@ async def test_vacuum_state_trigger_behavior_first(
assert len(service_calls) == 0
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
@pytest.mark.usefixtures("enable_labs_preview_features")
@pytest.mark.parametrize(
("trigger_target_config", "entity_id", "entities_in_target"),
parametrize_target_entities("vacuum"),

View File

@@ -1,7 +1,6 @@
"""Tests for WebSocket API commands."""
import asyncio
from collections.abc import Generator
from copy import deepcopy
import io
import logging
@@ -79,16 +78,6 @@ STATE_KEY_SHORT_NAMES = {
STATE_KEY_LONG_NAMES = {v: k for k, v in STATE_KEY_SHORT_NAMES.items()}
@pytest.fixture(name="enable_experimental_triggers_conditions")
def enable_experimental_triggers_conditions() -> Generator[None]:
"""Enable experimental triggers and conditions."""
with patch(
"homeassistant.components.labs.async_is_preview_feature_enabled",
return_value=True,
):
yield
@pytest.fixture
def fake_integration(hass: HomeAssistant):
"""Set up a mock integration with device automation support."""
@@ -3661,7 +3650,7 @@ async def test_extract_from_target_validation_error(
assert "error" in msg
@pytest.mark.usefixtures("enable_experimental_triggers_conditions", "target_entities")
@pytest.mark.usefixtures("enable_labs_preview_features", "target_entities")
@patch("annotatedyaml.loader.load_yaml")
@pytest.mark.parametrize("automation_component", ["trigger", "condition"])
async def test_get_triggers_conditions_for_target(