Add config flow for Time & Date (#104183)

Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
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.""" """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 typing import Final
from homeassistant.const import Platform
CONF_DISPLAY_OPTIONS = "display_options"
DOMAIN: Final = "time_date" 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", "domain": "time_date",
"name": "Time & Date", "name": "Time & Date",
"codeowners": ["@fabaff"], "codeowners": ["@fabaff"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/time_date", "documentation": "https://www.home-assistant.io/integrations/time_date",
"integration_type": "service",
"iot_class": "local_push", "iot_class": "local_push",
"quality_scale": "internal" "quality_scale": "internal"
} }

View file

@ -1,12 +1,19 @@
"""Support for showing the date and the time.""" """Support for showing the date and the time."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Mapping
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from typing import Any
import voluptuous as vol 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.const import CONF_DISPLAY_OPTIONS, EVENT_CORE_CONFIG_UPDATE
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
import homeassistant.helpers.config_validation as cv 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 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from .const import DOMAIN from .const import DOMAIN, OPTION_TYPES
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TIME_STR_FORMAT = "%H:%M" 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( 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") _LOGGER.warning("'beat': is deprecated and will be removed in version 2024.7")
async_add_entities( 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.""" """Implementation of a Time and Date sensor."""
_attr_should_poll = False _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.""" """Initialize the sensor."""
self._name = OPTION_TYPES[option_type] self._attr_translation_key = option_type
self.type = option_type self.type = option_type
self._state: str | None = None object_id = "internet_time" if option_type == "beat" else option_type
self.hass = hass self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self.unsub: CALLBACK_TYPE | None = None self._attr_unique_id = option_type if entry_id else None
@property self._update_internal_state(dt_util.utcnow())
def name(self) -> str:
"""Return the name of the sensor."""
return self._name
@property @property
def native_value(self) -> str | None: def native_value(self) -> str | None:
@ -107,6 +114,35 @@ class TimeDateSensor(SensorEntity):
return "mdi:calendar" return "mdi:calendar"
return "mdi:clock" 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: async def async_added_to_hass(self) -> None:
"""Set up first update.""" """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": { "issues": {
"deprecated_beat": { "deprecated_beat": {
"title": "The `{config_key}` Time & Date sensor is being removed", "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", "tibber",
"tile", "tile",
"tilt_ble", "tilt_ble",
"time_date",
"todoist", "todoist",
"tolo", "tolo",
"tomorrowio", "tomorrowio",

View file

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

View file

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

View file

@ -145,10 +145,6 @@ class EntityPlatform:
# which powers entity_component.add_entities # which powers entity_component.add_entities
self.parallel_updates_created = platform is None 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( self.domain_entities: dict[str, Entity] = hass.data.setdefault(
DATA_DOMAIN_ENTITIES, {} DATA_DOMAIN_ENTITIES, {}
).setdefault(domain, {}) ).setdefault(domain, {})
@ -310,44 +306,8 @@ class EntityPlatform:
logger = self.logger logger = self.logger
hass = self.hass hass = self.hass
full_name = f"{self.platform_name}.{self.domain}" 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( await self.async_load_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
)
logger.info("Setting up %s", full_name) logger.info("Setting up %s", full_name)
warn_task = hass.loop.call_at( warn_task = hass.loop.call_at(
@ -430,6 +390,48 @@ class EntityPlatform:
finally: finally:
warn_task.cancel() 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( def _schedule_add_entities(
self, new_entities: Iterable[Entity], update_before_add: bool = False self, new_entities: Iterable[Entity], update_before_add: bool = False
) -> None: ) -> None:
@ -783,6 +785,13 @@ class EntityPlatform:
self._async_unsub_polling() self._async_unsub_polling()
self._async_unsub_polling = None 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: async def async_destroy(self) -> None:
"""Destroy an entity platform. """Destroy an entity platform.

View file

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