Support restoring TextEntity native_value (#82770)
parent
fcccc44ccb
commit
258b9fe663
|
@ -1,7 +1,7 @@
|
|||
"""Component to allow setting text as platforms."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import asdict, dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import re
|
||||
|
@ -20,6 +20,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
|||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
|
@ -222,3 +223,47 @@ class TextEntity(Entity):
|
|||
async def async_set_value(self, value: str) -> None:
|
||||
"""Change the value."""
|
||||
await self.hass.async_add_executor_job(self.set_value, value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TextExtraStoredData(ExtraStoredData):
|
||||
"""Object to hold extra stored data."""
|
||||
|
||||
native_value: str | None
|
||||
native_min: int
|
||||
native_max: int
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Return a dict representation of the text data."""
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, restored: dict[str, Any]) -> TextExtraStoredData | None:
|
||||
"""Initialize a stored text state from a dict."""
|
||||
try:
|
||||
return cls(
|
||||
restored["native_value"],
|
||||
restored["native_min"],
|
||||
restored["native_max"],
|
||||
)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class RestoreText(TextEntity, RestoreEntity):
|
||||
"""Mixin class for restoring previous text state."""
|
||||
|
||||
@property
|
||||
def extra_restore_state_data(self) -> TextExtraStoredData:
|
||||
"""Return text specific state data to be restored."""
|
||||
return TextExtraStoredData(
|
||||
self.native_value,
|
||||
self.native_min,
|
||||
self.native_max,
|
||||
)
|
||||
|
||||
async def async_get_last_text_data(self) -> TextExtraStoredData | None:
|
||||
"""Restore attributes."""
|
||||
if (restored_last_extra_data := await self.async_get_last_extra_data()) is None:
|
||||
return None
|
||||
return TextExtraStoredData.from_dict(restored_last_extra_data.as_dict())
|
||||
|
|
|
@ -14,7 +14,11 @@ from homeassistant.components.text import (
|
|||
_async_set_value,
|
||||
)
|
||||
from homeassistant.const import MAX_LENGTH_STATE_STATE
|
||||
from homeassistant.core import ServiceCall
|
||||
from homeassistant.core import ServiceCall, State
|
||||
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import mock_restore_cache_with_extra_data
|
||||
|
||||
|
||||
class MockTextEntity(TextEntity):
|
||||
|
@ -95,10 +99,99 @@ async def test_text_set_value(hass):
|
|||
async def test_text_value_outside_bounds(hass):
|
||||
"""Test text entity with value that is outside min and max."""
|
||||
with pytest.raises(ValueError):
|
||||
MockTextEntity(
|
||||
_ = MockTextEntity(
|
||||
"hello world", native_min=2, native_max=5, pattern=r"[a-z]"
|
||||
).state
|
||||
with pytest.raises(ValueError):
|
||||
MockTextEntity(
|
||||
_ = MockTextEntity(
|
||||
"hello world", native_min=15, native_max=20, pattern=r"[a-z]"
|
||||
).state
|
||||
|
||||
|
||||
RESTORE_DATA = {
|
||||
"native_max": 5,
|
||||
"native_min": 1,
|
||||
# "mode": TextMode.TEXT,
|
||||
# "pattern": r"[A-Za-z0-9]",
|
||||
"native_value": "Hello",
|
||||
}
|
||||
|
||||
|
||||
async def test_restore_number_save_state(
|
||||
hass,
|
||||
hass_storage,
|
||||
enable_custom_integrations,
|
||||
):
|
||||
"""Test RestoreNumber."""
|
||||
platform = getattr(hass.components, "test.text")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES.append(
|
||||
platform.MockRestoreText(
|
||||
name="Test",
|
||||
native_max=5,
|
||||
native_min=1,
|
||||
native_value="Hello",
|
||||
)
|
||||
)
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
assert await async_setup_component(hass, "text", {"text": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Trigger saving state
|
||||
await hass.async_stop()
|
||||
|
||||
assert len(hass_storage[RESTORE_STATE_KEY]["data"]) == 1
|
||||
state = hass_storage[RESTORE_STATE_KEY]["data"][0]["state"]
|
||||
assert state["entity_id"] == entity0.entity_id
|
||||
extra_data = hass_storage[RESTORE_STATE_KEY]["data"][0]["extra_data"]
|
||||
assert extra_data == RESTORE_DATA
|
||||
assert isinstance(extra_data["native_value"], str)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"native_max, native_min, native_value, native_value_type, extra_data",
|
||||
[
|
||||
(5, 1, "Hello", str, RESTORE_DATA),
|
||||
(255, 1, None, type(None), None),
|
||||
(255, 1, None, type(None), {}),
|
||||
(255, 1, None, type(None), {"beer": 123}),
|
||||
(255, 1, None, type(None), {"native_value": {}}),
|
||||
],
|
||||
)
|
||||
async def test_restore_number_restore_state(
|
||||
hass,
|
||||
enable_custom_integrations,
|
||||
hass_storage,
|
||||
native_max,
|
||||
native_min,
|
||||
native_value,
|
||||
native_value_type,
|
||||
extra_data,
|
||||
):
|
||||
"""Test RestoreNumber."""
|
||||
mock_restore_cache_with_extra_data(hass, ((State("text.test", ""), extra_data),))
|
||||
|
||||
platform = getattr(hass.components, "test.text")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES.append(
|
||||
platform.MockRestoreText(
|
||||
native_max=native_max,
|
||||
native_min=native_min,
|
||||
name="Test",
|
||||
native_value=None,
|
||||
)
|
||||
)
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
assert await async_setup_component(hass, "text", {"text": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(entity0.entity_id)
|
||||
|
||||
assert entity0.native_max == native_max
|
||||
assert entity0.native_min == native_min
|
||||
assert entity0.mode == TextMode.TEXT
|
||||
assert entity0.pattern is None
|
||||
assert entity0.native_value == native_value
|
||||
assert isinstance(entity0.native_value, native_value_type)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
Provide a mock text platform.
|
||||
|
||||
Call init before using it in your tests to ensure clean test data.
|
||||
"""
|
||||
from homeassistant.components.text import RestoreText, TextEntity, TextMode
|
||||
|
||||
from tests.common import MockEntity
|
||||
|
||||
UNIQUE_TEXT = "unique_text"
|
||||
|
||||
ENTITIES = []
|
||||
|
||||
|
||||
class MockTextEntity(MockEntity, TextEntity):
|
||||
"""Mock text class."""
|
||||
|
||||
@property
|
||||
def native_max(self):
|
||||
"""Return the native native_max."""
|
||||
return self._handle("native_max")
|
||||
|
||||
@property
|
||||
def native_min(self):
|
||||
"""Return the native native_min."""
|
||||
return self._handle("native_min")
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Return the mode."""
|
||||
return self._handle("mode")
|
||||
|
||||
@property
|
||||
def pattern(self):
|
||||
"""Return the pattern."""
|
||||
return self._handle("pattern")
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the native value of this text."""
|
||||
return self._handle("native_value")
|
||||
|
||||
def set_native_value(self, value: str) -> None:
|
||||
"""Change the selected option."""
|
||||
self._values["native_value"] = value
|
||||
|
||||
|
||||
class MockRestoreText(MockTextEntity, RestoreText):
|
||||
"""Mock RestoreText class."""
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore native_*."""
|
||||
await super().async_added_to_hass()
|
||||
if (last_text_data := await self.async_get_last_text_data()) is None:
|
||||
return
|
||||
self._values["native_max"] = last_text_data.native_max
|
||||
self._values["native_min"] = last_text_data.native_min
|
||||
self._values["native_value"] = last_text_data.native_value
|
||||
|
||||
|
||||
def init(empty=False):
|
||||
"""Initialize the platform with entities."""
|
||||
global ENTITIES
|
||||
|
||||
ENTITIES = (
|
||||
[]
|
||||
if empty
|
||||
else [
|
||||
MockTextEntity(
|
||||
name="test",
|
||||
native_min=1,
|
||||
native_max=5,
|
||||
mode=TextMode.TEXT,
|
||||
pattern=r"[A-Za-z0-9]",
|
||||
unique_id=UNIQUE_TEXT,
|
||||
native_value="Hello",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities_callback, discovery_info=None
|
||||
):
|
||||
"""Return mock entities."""
|
||||
async_add_entities_callback(ENTITIES)
|
Loading…
Reference in New Issue