diff --git a/homeassistant/components/time_date/__init__.py b/homeassistant/components/time_date/__init__.py index 25e6fa14f396f0..cdd69a2bc1fcff 100644 --- a/homeassistant/components/time_date/__init__.py +++ b/homeassistant/components/time_date/__init__.py @@ -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) diff --git a/homeassistant/components/time_date/config_flow.py b/homeassistant/components/time_date/config_flow.py new file mode 100644 index 00000000000000..09a5f2503d08bc --- /dev/null +++ b/homeassistant/components/time_date/config_flow.py @@ -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 + ) diff --git a/homeassistant/components/time_date/const.py b/homeassistant/components/time_date/const.py index 4d0ff354a6c1c0..dde9497b9a3f68 100644 --- a/homeassistant/components/time_date/const.py +++ b/homeassistant/components/time_date/const.py @@ -3,4 +3,20 @@ 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", +] diff --git a/homeassistant/components/time_date/manifest.json b/homeassistant/components/time_date/manifest.json index 9d625b8587e07b..9247b60568aa1e 100644 --- a/homeassistant/components/time_date/manifest.json +++ b/homeassistant/components/time_date/manifest.json @@ -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" } diff --git a/homeassistant/components/time_date/sensor.py b/homeassistant/components/time_date/sensor.py index c00d362428b792..bd0f9449aea1af 100644 --- a/homeassistant/components/time_date/sensor.py +++ b/homeassistant/components/time_date/sensor.py @@ -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.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 @@ def icon(self) -> str: 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.""" diff --git a/homeassistant/components/time_date/strings.json b/homeassistant/components/time_date/strings.json index 582fd44a45bf57..e9efe949b9ba43 100644 --- a/homeassistant/components/time_date/strings.json +++ b/homeassistant/components/time_date/strings.json @@ -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." + } + } + } } } } diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 254a3ad0df3c05..fe5e746a8355b0 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -510,6 +510,7 @@ "tibber", "tile", "tilt_ble", + "time_date", "todoist", "tolo", "tomorrowio", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index c55b6aecce9712..58bafa60691741 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -5975,9 +5975,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": { @@ -6999,6 +6998,7 @@ "switch_as_x", "tag", "threshold", + "time_date", "tod", "uptime", "utility_meter", diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index b3eb8722997f60..f9940b526ed096 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -373,7 +373,7 @@ def _async_init_entity_platform( if scan_interval is None: scan_interval = self.scan_interval - return EntityPlatform( + entity_platform = EntityPlatform( hass=self.hass, logger=self.logger, domain=self.domain, @@ -382,6 +382,8 @@ def _async_init_entity_platform( 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.""" diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 1bf7d95135ba87..03e78b6bfeaa68 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -144,10 +144,6 @@ def __init__( # 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, {}) @@ -309,44 +305,8 @@ async def _async_setup_platform( 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( @@ -429,6 +389,48 @@ async def setup_again(*_args: Any) -> None: 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: @@ -782,6 +784,13 @@ def async_unsub_polling(self) -> None: 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. diff --git a/tests/components/time_date/__init__.py b/tests/components/time_date/__init__.py index 22734c19bbb85d..9817271a8d97f8 100644 --- a/tests/components/time_date/__init__.py +++ b/tests/components/time_date/__init__.py @@ -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 diff --git a/tests/components/time_date/conftest.py b/tests/components/time_date/conftest.py new file mode 100644 index 00000000000000..af732f978b4a58 --- /dev/null +++ b/tests/components/time_date/conftest.py @@ -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 diff --git a/tests/components/time_date/test_config_flow.py b/tests/components/time_date/test_config_flow.py new file mode 100644 index 00000000000000..228a34b65b4957 --- /dev/null +++ b/tests/components/time_date/test_config_flow.py @@ -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 diff --git a/tests/components/time_date/test_init.py b/tests/components/time_date/test_init.py new file mode 100644 index 00000000000000..cd7c5044201197 --- /dev/null +++ b/tests/components/time_date/test_init.py @@ -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 diff --git a/tests/components/time_date/test_sensor.py b/tests/components/time_date/test_sensor.py index e8741a4342776c..d7e87b3a471971 100644 --- a/tests/components/time_date/test_sensor.py +++ b/tests/components/time_date/test_sensor.py @@ -5,17 +5,15 @@ 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}} - - await async_setup_component(hass, "sensor", config) + config = { + "sensor": { + "platform": "time_date", + "display_options": display_options, + } + } + + 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"]