Add config flow for Time & Date (#104183)
Co-authored-by: Erik <erik@montnemery.com>pull/108715/head
parent
eaa32146a6
commit
65581e94ea
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -520,6 +520,7 @@ FLOWS = {
|
|||
"tibber",
|
||||
"tile",
|
||||
"tilt_ble",
|
||||
"time_date",
|
||||
"todoist",
|
||||
"tolo",
|
||||
"tomorrowio",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"]
|
||||
|
|
Loading…
Reference in New Issue