Add config flow for Time & Date (#104183)

Co-authored-by: Erik <erik@montnemery.com>
pull/108715/head
G Johansson 2024-01-23 12:18:31 +01:00 committed by GitHub
parent eaa32146a6
commit 65581e94ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 573 additions and 88 deletions

View File

@ -1 +1,18 @@
"""The time_date component."""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import PLATFORMS
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Time & Date from a config entry."""
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Time & Date config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -0,0 +1,130 @@
"""Adds config flow for Time & Date integration."""
from __future__ import annotations
from collections.abc import Mapping
from datetime import timedelta
import logging
from typing import Any
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
SchemaFlowError,
SchemaFlowFormStep,
)
from homeassistant.helpers.selector import (
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
)
from homeassistant.setup import async_prepare_setup_platform
from .const import CONF_DISPLAY_OPTIONS, DOMAIN, OPTION_TYPES
from .sensor import TimeDateSensor
_LOGGER = logging.getLogger(__name__)
USER_SCHEMA = vol.Schema(
{
vol.Required(CONF_DISPLAY_OPTIONS): SelectSelector(
SelectSelectorConfig(
options=[option for option in OPTION_TYPES if option != "beat"],
mode=SelectSelectorMode.DROPDOWN,
translation_key="display_options",
)
),
}
)
async def validate_input(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Validate rest setup."""
hass = handler.parent_handler.hass
if hass.config.time_zone is None:
raise SchemaFlowError("timezone_not_exist")
return user_input
CONFIG_FLOW = {
"user": SchemaFlowFormStep(
schema=USER_SCHEMA,
preview=DOMAIN,
validate_user_input=validate_input,
)
}
class TimeDateConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config flow for Time & Date."""
config_flow = CONFIG_FLOW
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
"""Return config entry title."""
return f"Time & Date {options[CONF_DISPLAY_OPTIONS]}"
def async_config_flow_finished(self, options: Mapping[str, Any]) -> None:
"""Abort if instance already exist."""
self._async_abort_entries_match(dict(options))
@staticmethod
async def async_setup_preview(hass: HomeAssistant) -> None:
"""Set up preview WS API."""
websocket_api.async_register_command(hass, ws_start_preview)
@websocket_api.websocket_command(
{
vol.Required("type"): "time_date/start_preview",
vol.Required("flow_id"): str,
vol.Required("flow_type"): vol.Any("config_flow"),
vol.Required("user_input"): dict,
}
)
@websocket_api.async_response
async def ws_start_preview(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Generate a preview."""
validated = USER_SCHEMA(msg["user_input"])
# Create an EntityPlatform, needed for name translations
platform = await async_prepare_setup_platform(hass, {}, SENSOR_DOMAIN, DOMAIN)
entity_platform = EntityPlatform(
hass=hass,
logger=_LOGGER,
domain=SENSOR_DOMAIN,
platform_name=DOMAIN,
platform=platform,
scan_interval=timedelta(seconds=3600),
entity_namespace=None,
)
await entity_platform.async_load_translations()
@callback
def async_preview_updated(state: str, attributes: Mapping[str, Any]) -> None:
"""Forward config entry state events to websocket."""
connection.send_message(
websocket_api.event_message(
msg["id"], {"attributes": attributes, "state": state}
)
)
preview_entity = TimeDateSensor(validated[CONF_DISPLAY_OPTIONS])
preview_entity.hass = hass
preview_entity.platform = entity_platform
connection.send_result(msg["id"])
connection.subscriptions[msg["id"]] = preview_entity.async_start_preview(
async_preview_updated
)

View File

@ -3,4 +3,20 @@ from __future__ import annotations
from typing import Final
from homeassistant.const import Platform
CONF_DISPLAY_OPTIONS = "display_options"
DOMAIN: Final = "time_date"
PLATFORMS = [Platform.SENSOR]
TIME_STR_FORMAT = "%H:%M"
OPTION_TYPES = [
"time",
"date",
"date_time",
"date_time_utc",
"date_time_iso",
"time_date",
"beat",
"time_utc",
]

View File

@ -2,7 +2,9 @@
"domain": "time_date",
"name": "Time & Date",
"codeowners": ["@fabaff"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/time_date",
"integration_type": "service",
"iot_class": "local_push",
"quality_scale": "internal"
}

View File

@ -1,12 +1,19 @@
"""Support for showing the date and the time."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from datetime import datetime, timedelta
import logging
from typing import Any
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.sensor import (
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DISPLAY_OPTIONS, EVENT_CORE_CONFIG_UPDATE
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
@ -16,22 +23,12 @@ from homeassistant.helpers.issue_registry import IssueSeverity, async_create_iss
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util
from .const import DOMAIN
from .const import DOMAIN, OPTION_TYPES
_LOGGER = logging.getLogger(__name__)
TIME_STR_FORMAT = "%H:%M"
OPTION_TYPES = {
"time": "Time",
"date": "Date",
"date_time": "Date & Time",
"date_time_utc": "Date & Time (UTC)",
"date_time_iso": "Date & Time (ISO)",
"time_date": "Time & Date",
"beat": "Internet Time",
"time_utc": "Time (UTC)",
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
@ -71,7 +68,17 @@ async def async_setup_platform(
_LOGGER.warning("'beat': is deprecated and will be removed in version 2024.7")
async_add_entities(
[TimeDateSensor(hass, variable) for variable in config[CONF_DISPLAY_OPTIONS]]
[TimeDateSensor(variable) for variable in config[CONF_DISPLAY_OPTIONS]]
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Time & Date sensor."""
async_add_entities(
[TimeDateSensor(entry.options[CONF_DISPLAY_OPTIONS], entry.entry_id)]
)
@ -79,19 +86,19 @@ class TimeDateSensor(SensorEntity):
"""Implementation of a Time and Date sensor."""
_attr_should_poll = False
_attr_has_entity_name = True
_state: str | None = None
unsub: CALLBACK_TYPE | None = None
def __init__(self, hass: HomeAssistant, option_type: str) -> None:
def __init__(self, option_type: str, entry_id: str | None = None) -> None:
"""Initialize the sensor."""
self._name = OPTION_TYPES[option_type]
self._attr_translation_key = option_type
self.type = option_type
self._state: str | None = None
self.hass = hass
self.unsub: CALLBACK_TYPE | None = None
object_id = "internet_time" if option_type == "beat" else option_type
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._attr_unique_id = option_type if entry_id else None
@property
def name(self) -> str:
"""Return the name of the sensor."""
return self._name
self._update_internal_state(dt_util.utcnow())
@property
def native_value(self) -> str | None:
@ -107,6 +114,35 @@ class TimeDateSensor(SensorEntity):
return "mdi:calendar"
return "mdi:clock"
@callback
def async_start_preview(
self,
preview_callback: Callable[[str, Mapping[str, Any]], None],
) -> CALLBACK_TYPE:
"""Render a preview."""
@callback
def point_in_time_listener(time_date: datetime | None) -> None:
"""Update preview."""
now = dt_util.utcnow()
self._update_internal_state(now)
self.unsub = async_track_point_in_utc_time(
self.hass, point_in_time_listener, self.get_next_interval(now)
)
calculated_state = self._async_calculate_state()
preview_callback(calculated_state.state, calculated_state.attributes)
@callback
def async_stop_preview() -> None:
"""Stop preview."""
if self.unsub:
self.unsub()
self.unsub = None
point_in_time_listener(None)
return async_stop_preview
async def async_added_to_hass(self) -> None:
"""Set up first update."""

View File

@ -1,8 +1,83 @@
{
"title": "Time & Date",
"config": {
"abort": {
"already_configured": "The chosen Time & Date sensor has already been configured"
},
"step": {
"user": {
"description": "Select from the sensor options below",
"data": {
"display_options": "Sensor type"
}
}
},
"error": {
"timezone_not_exist": "Timezone is not set in Home Assistant configuration"
}
},
"options": {
"step": {
"init": {
"data": {
"display_options": "[%key:component::time_date::config::step::user::data::display_options%]"
}
}
}
},
"selector": {
"display_options": {
"options": {
"time": "Time",
"date": "Date",
"date_time": "Date & Time",
"date_time_utc": "Date & Time (UTC)",
"date_time_iso": "Date & Time (ISO)",
"time_date": "Time & Date",
"beat": "Internet time",
"time_utc": "Time (UTC)"
}
}
},
"entity": {
"sensor": {
"time": {
"name": "[%key:component::time_date::selector::display_options::options::time%]"
},
"date": {
"name": "[%key:component::time_date::selector::display_options::options::date%]"
},
"date_time": {
"name": "[%key:component::time_date::selector::display_options::options::date_time%]"
},
"date_time_utc": {
"name": "[%key:component::time_date::selector::display_options::options::date_time_utc%]"
},
"date_time_iso": {
"name": "[%key:component::time_date::selector::display_options::options::date_time_iso%]"
},
"time_date": {
"name": "[%key:component::time_date::selector::display_options::options::time_date%]"
},
"beat": {
"name": "[%key:component::time_date::selector::display_options::options::beat%]"
},
"time_utc": {
"name": "[%key:component::time_date::selector::display_options::options::time_utc%]"
}
}
},
"issues": {
"deprecated_beat": {
"title": "The `{config_key}` Time & Date sensor is being removed",
"description": "Please remove the `{config_key}` key from the `{display_options}` for the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue."
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::time_date::issues::deprecated_beat::title%]",
"description": "Please remove the `{config_key}` key from the {integration} config entry options and click submit to fix this issue."
}
}
}
}
}
}

View File

@ -520,6 +520,7 @@ FLOWS = {
"tibber",
"tile",
"tilt_ble",
"time_date",
"todoist",
"tolo",
"tomorrowio",

View File

@ -6026,9 +6026,8 @@
"iot_class": "local_push"
},
"time_date": {
"name": "Time & Date",
"integration_type": "hub",
"config_flow": false,
"integration_type": "service",
"config_flow": true,
"iot_class": "local_push"
},
"tmb": {
@ -7051,6 +7050,7 @@
"switch_as_x",
"tag",
"threshold",
"time_date",
"tod",
"uptime",
"utility_meter",

View File

@ -382,7 +382,7 @@ class EntityComponent(Generic[_EntityT]):
if scan_interval is None:
scan_interval = self.scan_interval
return EntityPlatform(
entity_platform = EntityPlatform(
hass=self.hass,
logger=self.logger,
domain=self.domain,
@ -391,6 +391,8 @@ class EntityComponent(Generic[_EntityT]):
scan_interval=scan_interval,
entity_namespace=entity_namespace,
)
entity_platform.async_prepare()
return entity_platform
async def _async_shutdown(self, event: Event) -> None:
"""Call when Home Assistant is stopping."""

View File

@ -145,10 +145,6 @@ class EntityPlatform:
# which powers entity_component.add_entities
self.parallel_updates_created = platform is None
hass.data.setdefault(DATA_ENTITY_PLATFORM, {}).setdefault(
self.platform_name, []
).append(self)
self.domain_entities: dict[str, Entity] = hass.data.setdefault(
DATA_DOMAIN_ENTITIES, {}
).setdefault(domain, {})
@ -310,44 +306,8 @@ class EntityPlatform:
logger = self.logger
hass = self.hass
full_name = f"{self.platform_name}.{self.domain}"
object_id_language = (
hass.config.language
if hass.config.language in languages.NATIVE_ENTITY_IDS
else languages.DEFAULT_LANGUAGE
)
async def get_translations(
language: str, category: str, integration: str
) -> dict[str, Any]:
"""Get entity translations."""
try:
return await translation.async_get_translations(
hass, language, category, {integration}
)
except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.debug(
"Could not load translations for %s",
integration,
exc_info=err,
)
return {}
self.component_translations = await get_translations(
hass.config.language, "entity_component", self.domain
)
self.platform_translations = await get_translations(
hass.config.language, "entity", self.platform_name
)
if object_id_language == hass.config.language:
self.object_id_component_translations = self.component_translations
self.object_id_platform_translations = self.platform_translations
else:
self.object_id_component_translations = await get_translations(
object_id_language, "entity_component", self.domain
)
self.object_id_platform_translations = await get_translations(
object_id_language, "entity", self.platform_name
)
await self.async_load_translations()
logger.info("Setting up %s", full_name)
warn_task = hass.loop.call_at(
@ -430,6 +390,48 @@ class EntityPlatform:
finally:
warn_task.cancel()
async def async_load_translations(self) -> None:
"""Load translations."""
hass = self.hass
object_id_language = (
hass.config.language
if hass.config.language in languages.NATIVE_ENTITY_IDS
else languages.DEFAULT_LANGUAGE
)
async def get_translations(
language: str, category: str, integration: str
) -> dict[str, Any]:
"""Get entity translations."""
try:
return await translation.async_get_translations(
hass, language, category, {integration}
)
except Exception as err: # pylint: disable=broad-exception-caught
_LOGGER.debug(
"Could not load translations for %s",
integration,
exc_info=err,
)
return {}
self.component_translations = await get_translations(
hass.config.language, "entity_component", self.domain
)
self.platform_translations = await get_translations(
hass.config.language, "entity", self.platform_name
)
if object_id_language == hass.config.language:
self.object_id_component_translations = self.component_translations
self.object_id_platform_translations = self.platform_translations
else:
self.object_id_component_translations = await get_translations(
object_id_language, "entity_component", self.domain
)
self.object_id_platform_translations = await get_translations(
object_id_language, "entity", self.platform_name
)
def _schedule_add_entities(
self, new_entities: Iterable[Entity], update_before_add: bool = False
) -> None:
@ -783,6 +785,13 @@ class EntityPlatform:
self._async_unsub_polling()
self._async_unsub_polling = None
@callback
def async_prepare(self) -> None:
"""Register the entity platform in DATA_ENTITY_PLATFORM."""
self.hass.data.setdefault(DATA_ENTITY_PLATFORM, {}).setdefault(
self.platform_name, []
).append(self)
async def async_destroy(self) -> None:
"""Destroy an entity platform.

View File

@ -1 +1,30 @@
"""Tests for the time_date component."""
from homeassistant.components.time_date.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_DISPLAY_OPTIONS
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def load_int(
hass: HomeAssistant, display_option: str | None = None
) -> MockConfigEntry:
"""Set up the Time & Date integration in Home Assistant."""
if display_option is None:
display_option = "time"
config_entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data={},
options={CONF_DISPLAY_OPTIONS: display_option},
entry_id=f"1234567890_{display_option}",
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry

View File

@ -0,0 +1,14 @@
"""Fixtures for Time & Date integration tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Mock setting up a config entry."""
with patch(
"homeassistant.components.time_date.async_setup_entry", return_value=True
) as mock_setup:
yield mock_setup

View File

@ -0,0 +1,138 @@
"""Test the Time & Date config flow."""
from __future__ import annotations
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
import pytest
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.time_date.const import CONF_DISPLAY_OPTIONS, DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.typing import WebSocketGenerator
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the forms."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"display_options": "time"},
)
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1
assert result["type"] == FlowResultType.CREATE_ENTRY
async def test_user_flow_does_not_allow_beat(
hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test we get the forms."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
with pytest.raises(vol.Invalid):
await hass.config_entries.flow.async_configure(
result["flow_id"],
{"display_options": ["beat"]},
)
async def test_single_instance(hass: HomeAssistant) -> None:
"""Test we get the forms."""
entry = MockConfigEntry(
domain=DOMAIN, data={}, options={CONF_DISPLAY_OPTIONS: "time"}
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"display_options": "time"},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_timezone_not_set(hass: HomeAssistant) -> None:
"""Test time zone not set."""
hass.config.time_zone = None
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"display_options": "time"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "timezone_not_exist"}
async def test_config_flow_preview(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test the config flow preview."""
client = await hass_ws_client(hass)
freezer.move_to("2024-01-02 20:14:11.672")
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] is None
assert result["preview"] == "time_date"
await client.send_json_auto_id(
{
"type": "time_date/start_preview",
"flow_id": result["flow_id"],
"flow_type": "config_flow",
"user_input": {"display_options": "time"},
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
msg = await client.receive_json()
assert msg["event"] == {
"attributes": {"friendly_name": "Time", "icon": "mdi:clock"},
"state": "12:14",
}
freezer.tick(60)
async_fire_time_changed(hass)
await hass.async_block_till_done()
msg = await client.receive_json()
assert msg["event"] == {
"attributes": {"friendly_name": "Time", "icon": "mdi:clock"},
"state": "12:15",
}
assert len(hass.states.async_all()) == 0

View File

@ -0,0 +1,18 @@
"""The tests for the Time & Date component."""
from homeassistant.core import HomeAssistant
from . import load_int
async def test_setup_and_remove_config_entry(hass: HomeAssistant) -> None:
"""Test setting up and removing a config entry."""
entry = await load_int(hass)
state = hass.states.get("sensor.time")
assert state is not None
assert await hass.config_entries.async_remove(entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("sensor.time") is None

View File

@ -5,17 +5,15 @@ from unittest.mock import ANY, Mock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.time_date.const import DOMAIN
import homeassistant.components.time_date.sensor as time_date
from homeassistant.components.time_date.const import DOMAIN, OPTION_TYPES
from homeassistant.core import HomeAssistant
from homeassistant.helpers import event, issue_registry as ir
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from tests.common import async_fire_time_changed
from . import load_int
ALL_DISPLAY_OPTIONS = list(time_date.OPTION_TYPES.keys())
CONFIG = {"sensor": {"platform": "time_date", "display_options": ALL_DISPLAY_OPTIONS}}
from tests.common import async_fire_time_changed
@patch("homeassistant.components.time_date.sensor.async_track_point_in_utc_time")
@ -54,12 +52,9 @@ async def test_intervals(
) -> None:
"""Test timing intervals of sensors when time zone is UTC."""
hass.config.set_time_zone("UTC")
config = {"sensor": {"platform": "time_date", "display_options": [display_option]}}
freezer.move_to(start_time)
await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
await load_int(hass, display_option)
mock_track_interval.assert_called_once_with(hass, ANY, tracked_time)
@ -70,8 +65,8 @@ async def test_states(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> No
now = dt_util.utc_from_timestamp(1495068856)
freezer.move_to(now)
await async_setup_component(hass, "sensor", CONFIG)
await hass.async_block_till_done()
for option in OPTION_TYPES:
await load_int(hass, option)
state = hass.states.get("sensor.time")
assert state.state == "00:54"
@ -130,8 +125,8 @@ async def test_states_non_default_timezone(
now = dt_util.utc_from_timestamp(1495068856)
freezer.move_to(now)
await async_setup_component(hass, "sensor", CONFIG)
await hass.async_block_till_done()
for option in OPTION_TYPES:
await load_int(hass, option)
state = hass.states.get("sensor.time")
assert state.state == "20:54"
@ -262,9 +257,7 @@ async def test_timezone_intervals(
hass.config.set_time_zone(time_zone)
freezer.move_to(start_time)
config = {"sensor": {"platform": "time_date", "display_options": ["date"]}}
await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
await load_int(hass, "date")
mock_track_interval.assert_called_once()
next_time = mock_track_interval.mock_calls[0][1][2]
@ -274,8 +267,8 @@ async def test_timezone_intervals(
async def test_icons(hass: HomeAssistant) -> None:
"""Test attributes of sensors."""
await async_setup_component(hass, "sensor", CONFIG)
await hass.async_block_till_done()
for option in OPTION_TYPES:
await load_int(hass, option)
state = hass.states.get("sensor.time")
assert state.attributes["icon"] == "mdi:clock"
@ -313,9 +306,14 @@ async def test_deprecation_warning(
expected_issues: list[str],
) -> None:
"""Test deprecation warning for swatch beat."""
config = {"sensor": {"platform": "time_date", "display_options": display_options}}
config = {
"sensor": {
"platform": "time_date",
"display_options": display_options,
}
}
await async_setup_component(hass, "sensor", config)
assert await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
warnings = [record for record in caplog.records if record.levelname == "WARNING"]