Support restoring TextEntity native_value (#82770)

pull/82907/head
Raman Gupta 2022-11-29 04:09:41 -05:00 committed by GitHub
parent fcccc44ccb
commit 258b9fe663
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 228 additions and 4 deletions

View File

@ -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())

View File

@ -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)

View File

@ -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)