Add text
platform (#79454)
Co-authored-by: Franck Nijhof <frenck@frenck.nl> Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
9132c42037
commit
003e4224c8
14 changed files with 567 additions and 0 deletions
|
@ -1156,6 +1156,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/template/ @PhracturedBlue @tetienne @home-assistant/core
|
||||
/homeassistant/components/tesla_wall_connector/ @einarhauks
|
||||
/tests/components/tesla_wall_connector/ @einarhauks
|
||||
/homeassistant/components/text/ @home-assistant/core
|
||||
/tests/components/text/ @home-assistant/core
|
||||
/homeassistant/components/tfiac/ @fredrike @mellado
|
||||
/homeassistant/components/thermobeacon/ @bdraco
|
||||
/tests/components/thermobeacon/ @bdraco
|
||||
|
|
|
@ -52,6 +52,7 @@ COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM = [
|
|||
Platform.SENSOR,
|
||||
Platform.SIREN,
|
||||
Platform.SWITCH,
|
||||
Platform.TEXT,
|
||||
Platform.UPDATE,
|
||||
Platform.VACUUM,
|
||||
Platform.WATER_HEATER,
|
||||
|
|
101
homeassistant/components/demo/text.py
Normal file
101
homeassistant/components/demo/text.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
"""Demo platform that offers a fake text entity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.text import TextEntity, TextMode
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the demo Text entity."""
|
||||
async_add_entities(
|
||||
[
|
||||
DemoText(
|
||||
unique_id="text",
|
||||
name="Text",
|
||||
icon=None,
|
||||
native_value="Hello world",
|
||||
),
|
||||
DemoText(
|
||||
unique_id="password",
|
||||
name="Password",
|
||||
icon="mdi:text",
|
||||
native_value="Hello world",
|
||||
mode=TextMode.PASSWORD,
|
||||
),
|
||||
DemoText(
|
||||
unique_id="text_1_to_5_char",
|
||||
name="Text with 1 to 5 characters",
|
||||
icon="mdi:text",
|
||||
native_value="Hello",
|
||||
native_min=1,
|
||||
native_max=5,
|
||||
),
|
||||
DemoText(
|
||||
unique_id="text_lowercase",
|
||||
name="Text with only lower case characters",
|
||||
icon="mdi:text",
|
||||
native_value="world",
|
||||
pattern=r"[a-z]+",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Demo config entry."""
|
||||
await async_setup_platform(hass, {}, async_add_entities)
|
||||
|
||||
|
||||
class DemoText(TextEntity):
|
||||
"""Representation of a demo text entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
unique_id: str,
|
||||
name: str,
|
||||
icon: str | None,
|
||||
native_value: str | None,
|
||||
mode: TextMode = TextMode.TEXT,
|
||||
native_max: int | None = None,
|
||||
native_min: int | None = None,
|
||||
pattern: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize the Demo text entity."""
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_name = name or DEVICE_DEFAULT_NAME
|
||||
self._attr_native_value = native_value
|
||||
self._attr_icon = icon
|
||||
self._attr_mode = mode
|
||||
if native_max is not None:
|
||||
self._attr_native_max = native_max
|
||||
if native_min is not None:
|
||||
self._attr_native_min = native_min
|
||||
if pattern is not None:
|
||||
self._attr_pattern = pattern
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, unique_id)},
|
||||
name=name,
|
||||
)
|
||||
|
||||
async def async_set_value(self, value: str) -> None:
|
||||
"""Update the value."""
|
||||
self._attr_native_value = value
|
||||
self.async_write_ha_state()
|
224
homeassistant/components/text/__init__.py
Normal file
224
homeassistant/components/text/__init__.py
Normal file
|
@ -0,0 +1,224 @@
|
|||
"""Component to allow setting text as platforms."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, final
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import MAX_LENGTH_STATE_STATE
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ATTR_MAX,
|
||||
ATTR_MIN,
|
||||
ATTR_MODE,
|
||||
ATTR_PATTERN,
|
||||
ATTR_VALUE,
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ["DOMAIN", "TextEntity", "TextEntityDescription", "TextMode"]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Text entities."""
|
||||
component = hass.data[DOMAIN] = EntityComponent[TextEntity](
|
||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
|
||||
)
|
||||
await component.async_setup(config)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_VALUE,
|
||||
{vol.Required(ATTR_VALUE): cv.string},
|
||||
_async_set_value,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _async_set_value(entity: TextEntity, service_call: ServiceCall) -> None:
|
||||
"""Service call wrapper to set a new value."""
|
||||
value = service_call.data[ATTR_VALUE]
|
||||
if len(value) < entity.min:
|
||||
raise ValueError(
|
||||
f"Value {value} for {entity.name} is too short (minimum length {entity.min})"
|
||||
)
|
||||
if len(value) > entity.max:
|
||||
raise ValueError(
|
||||
f"Value {value} for {entity.name} is too long (maximum length {entity.max})"
|
||||
)
|
||||
if entity.pattern_cmp and not entity.pattern_cmp.match(value):
|
||||
raise ValueError(
|
||||
f"Value {value} for {entity.name} doesn't match pattern {entity.pattern}"
|
||||
)
|
||||
await entity.async_set_value(value)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up a config entry."""
|
||||
component: EntityComponent[TextEntity] = hass.data[DOMAIN]
|
||||
return await component.async_setup_entry(entry)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
component: EntityComponent[TextEntity] = hass.data[DOMAIN]
|
||||
return await component.async_unload_entry(entry)
|
||||
|
||||
|
||||
class TextMode(StrEnum):
|
||||
"""Modes for text entities."""
|
||||
|
||||
PASSWORD = "password"
|
||||
TEXT = "text"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TextEntityDescription(EntityDescription):
|
||||
"""A class that describes text entities."""
|
||||
|
||||
native_min: int = 0
|
||||
native_max: int = MAX_LENGTH_STATE_STATE
|
||||
mode: TextMode = TextMode.TEXT
|
||||
pattern: str | None = None
|
||||
|
||||
|
||||
class TextEntity(Entity):
|
||||
"""Representation of a Text entity."""
|
||||
|
||||
entity_description: TextEntityDescription
|
||||
_attr_mode: TextMode
|
||||
_attr_native_value: str | None
|
||||
_attr_native_min: int
|
||||
_attr_native_max: int
|
||||
_attr_pattern: str | None
|
||||
_attr_state: None = None
|
||||
__pattern_cmp: re.Pattern | None = None
|
||||
|
||||
@property
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes."""
|
||||
return {
|
||||
ATTR_MODE: self.mode,
|
||||
ATTR_MIN: self.min,
|
||||
ATTR_MAX: self.max,
|
||||
ATTR_PATTERN: self.pattern,
|
||||
}
|
||||
|
||||
@property
|
||||
@final
|
||||
def state(self) -> str | None:
|
||||
"""Return the entity state."""
|
||||
if self.native_value is None:
|
||||
return None
|
||||
if len(self.native_value) < self.min:
|
||||
raise ValueError(
|
||||
f"Entity {self.entity_id} provides state {self.native_value} which is "
|
||||
f"too short (minimum length {self.min})"
|
||||
)
|
||||
if len(self.native_value) > self.max:
|
||||
raise ValueError(
|
||||
f"Entity {self.entity_id} provides state {self.native_value} which is "
|
||||
f"too long (maximum length {self.max})"
|
||||
)
|
||||
if self.pattern_cmp and not self.pattern_cmp.match(self.native_value):
|
||||
raise ValueError(
|
||||
f"Entity {self.entity_id} provides state {self.native_value} which "
|
||||
f"does not match expected pattern {self.pattern}"
|
||||
)
|
||||
return self.native_value
|
||||
|
||||
@property
|
||||
def mode(self) -> TextMode:
|
||||
"""Return the mode of the entity."""
|
||||
if hasattr(self, "_attr_mode"):
|
||||
return self._attr_mode
|
||||
if hasattr(self, "entity_description"):
|
||||
return self.entity_description.mode
|
||||
return TextMode.TEXT
|
||||
|
||||
@property
|
||||
def native_min(self) -> int:
|
||||
"""Return the minimum length of the value."""
|
||||
if hasattr(self, "_attr_native_min"):
|
||||
return self._attr_native_min
|
||||
if hasattr(self, "entity_description"):
|
||||
return self.entity_description.native_min
|
||||
return 0
|
||||
|
||||
@property
|
||||
@final
|
||||
def min(self) -> int:
|
||||
"""Return the minimum length of the value."""
|
||||
return max(self.native_min, 0)
|
||||
|
||||
@property
|
||||
def native_max(self) -> int:
|
||||
"""Return the maximum length of the value."""
|
||||
if hasattr(self, "_attr_native_max"):
|
||||
return self._attr_native_max
|
||||
if hasattr(self, "entity_description"):
|
||||
return self.entity_description.native_max
|
||||
return MAX_LENGTH_STATE_STATE
|
||||
|
||||
@property
|
||||
@final
|
||||
def max(self) -> int:
|
||||
"""Return the maximum length of the value."""
|
||||
return min(self.native_max, MAX_LENGTH_STATE_STATE)
|
||||
|
||||
@property
|
||||
@final
|
||||
def pattern_cmp(self) -> re.Pattern | None:
|
||||
"""Return a compiled pattern."""
|
||||
if self.pattern is None:
|
||||
self.__pattern_cmp = None
|
||||
return None
|
||||
if not self.__pattern_cmp or self.pattern != self.__pattern_cmp.pattern:
|
||||
self.__pattern_cmp = re.compile(self.pattern)
|
||||
return self.__pattern_cmp
|
||||
|
||||
@property
|
||||
def pattern(self) -> str | None:
|
||||
"""Return the regex pattern that the value must match."""
|
||||
if hasattr(self, "_attr_pattern"):
|
||||
return self._attr_pattern
|
||||
if hasattr(self, "entity_description"):
|
||||
return self.entity_description.pattern
|
||||
return None
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
"""Return the value reported by the text."""
|
||||
return self._attr_native_value
|
||||
|
||||
def set_value(self, value: str) -> None:
|
||||
"""Change the value."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_set_value(self, value: str) -> None:
|
||||
"""Change the value."""
|
||||
await self.hass.async_add_executor_job(self.set_value, value)
|
11
homeassistant/components/text/const.py
Normal file
11
homeassistant/components/text/const.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
"""Provides the constants needed for the component."""
|
||||
|
||||
DOMAIN = "text"
|
||||
|
||||
ATTR_MAX = "max"
|
||||
ATTR_MIN = "min"
|
||||
ATTR_MODE = "mode"
|
||||
ATTR_PATTERN = "pattern"
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
SERVICE_SET_VALUE = "set_value"
|
8
homeassistant/components/text/manifest.json
Normal file
8
homeassistant/components/text/manifest.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"domain": "text",
|
||||
"name": "Text",
|
||||
"documentation": "https://www.home-assistant.io/integrations/text",
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"quality_scale": "internal",
|
||||
"integration_type": "entity"
|
||||
}
|
12
homeassistant/components/text/recorder.py
Normal file
12
homeassistant/components/text/recorder.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
"""Integration platform for recorder."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from . import ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_PATTERN
|
||||
|
||||
|
||||
@callback
|
||||
def exclude_attributes(hass: HomeAssistant) -> set[str]:
|
||||
"""Exclude static attributes from being recorded in the database."""
|
||||
return {ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_PATTERN}
|
14
homeassistant/components/text/services.yaml
Normal file
14
homeassistant/components/text/services.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
set_value:
|
||||
name: Set value
|
||||
description: Set value of a text entity.
|
||||
target:
|
||||
entity:
|
||||
domain: text
|
||||
fields:
|
||||
value:
|
||||
name: Value
|
||||
description: Value to set.
|
||||
required: true
|
||||
example: "Hello world!"
|
||||
selector:
|
||||
text:
|
3
homeassistant/components/text/strings.json
Normal file
3
homeassistant/components/text/strings.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"title": "Text"
|
||||
}
|
|
@ -49,6 +49,7 @@ class Platform(StrEnum):
|
|||
SIREN = "siren"
|
||||
STT = "stt"
|
||||
SWITCH = "switch"
|
||||
TEXT = "text"
|
||||
TTS = "tts"
|
||||
VACUUM = "vacuum"
|
||||
UPDATE = "update"
|
||||
|
|
44
tests/components/demo/test_text.py
Normal file
44
tests/components/demo/test_text.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
"""The tests for the demo text component."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.text import (
|
||||
ATTR_MAX,
|
||||
ATTR_MIN,
|
||||
ATTR_PATTERN,
|
||||
ATTR_VALUE,
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, MAX_LENGTH_STATE_STATE
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
ENTITY_TEXT = "text.text"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_demo_text(hass):
|
||||
"""Initialize setup demo text."""
|
||||
assert await async_setup_component(hass, DOMAIN, {"text": {"platform": "demo"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
def test_setup_params(hass):
|
||||
"""Test the initial parameters."""
|
||||
state = hass.states.get(ENTITY_TEXT)
|
||||
assert state.state == "Hello world"
|
||||
assert state.attributes[ATTR_MIN] == 0
|
||||
assert state.attributes[ATTR_MAX] == MAX_LENGTH_STATE_STATE
|
||||
assert state.attributes[ATTR_PATTERN] is None
|
||||
assert state.attributes[ATTR_MODE] == "text"
|
||||
|
||||
|
||||
async def test_set_value(hass):
|
||||
"""Test set value service."""
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: ENTITY_TEXT, ATTR_VALUE: "new"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get(ENTITY_TEXT)
|
||||
assert state.state == "new"
|
1
tests/components/text/__init__.py
Normal file
1
tests/components/text/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Tests for the text component."""
|
104
tests/components/text/test_init.py
Normal file
104
tests/components/text/test_init.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
"""The tests for the text component."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.text import (
|
||||
ATTR_MAX,
|
||||
ATTR_MIN,
|
||||
ATTR_MODE,
|
||||
ATTR_PATTERN,
|
||||
ATTR_VALUE,
|
||||
DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
TextEntity,
|
||||
TextMode,
|
||||
_async_set_value,
|
||||
)
|
||||
from homeassistant.const import MAX_LENGTH_STATE_STATE
|
||||
from homeassistant.core import ServiceCall
|
||||
|
||||
|
||||
class MockTextEntity(TextEntity):
|
||||
"""Mock text device to use in tests."""
|
||||
|
||||
def __init__(
|
||||
self, native_value="test", native_min=None, native_max=None, pattern=None
|
||||
):
|
||||
"""Initialize mock text entity."""
|
||||
self._attr_native_value = native_value
|
||||
if native_min is not None:
|
||||
self._attr_native_min = native_min
|
||||
if native_max is not None:
|
||||
self._attr_native_max = native_max
|
||||
if pattern is not None:
|
||||
self._attr_pattern = pattern
|
||||
|
||||
async def async_set_value(self, value: str) -> None:
|
||||
"""Set the value of the text."""
|
||||
self._attr_native_value = value
|
||||
|
||||
|
||||
async def test_text_default(hass):
|
||||
"""Test text entity with defaults."""
|
||||
text = MockTextEntity()
|
||||
text.hass = hass
|
||||
|
||||
assert text.capability_attributes == {
|
||||
ATTR_MIN: 0,
|
||||
ATTR_MAX: MAX_LENGTH_STATE_STATE,
|
||||
ATTR_MODE: TextMode.TEXT,
|
||||
ATTR_PATTERN: None,
|
||||
}
|
||||
assert text.pattern is None
|
||||
assert text.state == "test"
|
||||
|
||||
|
||||
async def test_text_new_min_max_pattern(hass):
|
||||
"""Test text entity with new min, max, and pattern."""
|
||||
text = MockTextEntity(native_min=-1, native_max=500, pattern=r"[a-z]")
|
||||
text.hass = hass
|
||||
|
||||
assert text.capability_attributes == {
|
||||
ATTR_MIN: 0,
|
||||
ATTR_MAX: MAX_LENGTH_STATE_STATE,
|
||||
ATTR_MODE: TextMode.TEXT,
|
||||
ATTR_PATTERN: r"[a-z]",
|
||||
}
|
||||
|
||||
|
||||
async def test_text_set_value(hass):
|
||||
"""Test text entity with set_value service."""
|
||||
text = MockTextEntity(native_min=1, native_max=5, pattern=r"[a-z]")
|
||||
text.hass = hass
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await _async_set_value(
|
||||
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: ""})
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await _async_set_value(
|
||||
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "hello world!"})
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await _async_set_value(
|
||||
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "HELLO"})
|
||||
)
|
||||
|
||||
await _async_set_value(
|
||||
text, ServiceCall(DOMAIN, SERVICE_SET_VALUE, {ATTR_VALUE: "test2"})
|
||||
)
|
||||
|
||||
assert text.state == "test2"
|
||||
|
||||
|
||||
async def test_text_value_outside_bounds(hass):
|
||||
"""Test text entity with value that is outside min and max."""
|
||||
with pytest.raises(ValueError):
|
||||
MockTextEntity(
|
||||
"hello world", native_min=2, native_max=5, pattern=r"[a-z]"
|
||||
).state
|
||||
with pytest.raises(ValueError):
|
||||
MockTextEntity(
|
||||
"hello world", native_min=15, native_max=20, pattern=r"[a-z]"
|
||||
).state
|
41
tests/components/text/test_recorder.py
Normal file
41
tests/components/text/test_recorder.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
"""The tests for text recorder."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components import text
|
||||
from homeassistant.components.recorder.db_schema import StateAttributes, States
|
||||
from homeassistant.components.recorder.util import session_scope
|
||||
from homeassistant.components.text import ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_PATTERN
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME
|
||||
from homeassistant.core import State
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
from tests.components.recorder.common import async_wait_recording_done
|
||||
|
||||
|
||||
async def test_exclude_attributes(recorder_mock, hass):
|
||||
"""Test siren registered attributes to be excluded."""
|
||||
await async_setup_component(hass, text.DOMAIN, {text.DOMAIN: {"platform": "demo"}})
|
||||
await hass.async_block_till_done()
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
||||
await hass.async_block_till_done()
|
||||
await async_wait_recording_done(hass)
|
||||
|
||||
def _fetch_states() -> list[State]:
|
||||
with session_scope(hass=hass) as session:
|
||||
native_states = []
|
||||
for db_state, db_state_attributes in session.query(States, StateAttributes):
|
||||
state = db_state.to_native()
|
||||
state.attributes = db_state_attributes.to_native()
|
||||
native_states.append(state)
|
||||
return native_states
|
||||
|
||||
states: list[State] = await hass.async_add_executor_job(_fetch_states)
|
||||
assert len(states) > 1
|
||||
for state in states:
|
||||
for attr in (ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_PATTERN):
|
||||
assert attr not in state.attributes
|
||||
assert ATTR_FRIENDLY_NAME in state.attributes
|
Loading…
Add table
Reference in a new issue