mirror of
https://github.com/home-assistant/core.git
synced 2025-11-11 03:50:55 +00:00
Compare commits
5 Commits
copilot/ad
...
input-week
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39d970347e | ||
|
|
9cccc96f63 | ||
|
|
a32ada3155 | ||
|
|
77f078e57d | ||
|
|
8657bfd0bf |
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@@ -753,6 +753,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/input_select/ @home-assistant/core
|
||||
/homeassistant/components/input_text/ @home-assistant/core
|
||||
/tests/components/input_text/ @home-assistant/core
|
||||
/homeassistant/components/input_weekday/ @home-assistant/core
|
||||
/tests/components/input_weekday/ @home-assistant/core
|
||||
/homeassistant/components/insteon/ @teharris1
|
||||
/tests/components/insteon/ @teharris1
|
||||
/homeassistant/components/integration/ @dgomes
|
||||
|
||||
@@ -231,6 +231,7 @@ DEFAULT_INTEGRATIONS = {
|
||||
"input_datetime",
|
||||
"input_number",
|
||||
"input_select",
|
||||
"input_weekday",
|
||||
"input_text",
|
||||
"schedule",
|
||||
"timer",
|
||||
|
||||
@@ -72,15 +72,21 @@ _TIME_TRIGGER_SCHEMA = vol.Any(
|
||||
),
|
||||
)
|
||||
|
||||
_WEEKDAY_SCHEMA = vol.Any(
|
||||
vol.In(WEEKDAYS),
|
||||
vol.All(cv.ensure_list, [vol.In(WEEKDAYS)]),
|
||||
cv.entity_domain(["input_weekday"]),
|
||||
msg=(
|
||||
"Expected a weekday (mon, tue, wed, thu, fri, sat, sun), "
|
||||
"a list of weekdays, or an Entity ID with domain 'input_weekday'"
|
||||
),
|
||||
)
|
||||
|
||||
TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_PLATFORM): "time",
|
||||
vol.Required(CONF_AT): vol.All(cv.ensure_list, [_TIME_TRIGGER_SCHEMA]),
|
||||
vol.Optional(CONF_WEEKDAY): vol.Any(
|
||||
vol.In(WEEKDAYS),
|
||||
vol.All(cv.ensure_list, [vol.In(WEEKDAYS)]),
|
||||
),
|
||||
vol.Optional(CONF_WEEKDAY): _WEEKDAY_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -117,7 +123,14 @@ async def async_attach_trigger( # noqa: C901
|
||||
|
||||
# Check if current weekday matches the configuration
|
||||
if isinstance(weekday_config, str):
|
||||
if current_weekday != weekday_config:
|
||||
# Could be a single weekday string or an entity_id
|
||||
if weekday_config.startswith("input_weekday."):
|
||||
if (weekday_state := hass.states.get(weekday_config)) is None:
|
||||
return
|
||||
entity_weekdays = weekday_state.attributes.get("weekdays", [])
|
||||
if current_weekday not in entity_weekdays:
|
||||
return
|
||||
elif current_weekday != weekday_config:
|
||||
return
|
||||
elif current_weekday not in weekday_config:
|
||||
return
|
||||
|
||||
285
homeassistant/components/input_weekday/__init__.py
Normal file
285
homeassistant/components/input_weekday/__init__.py
Normal file
@@ -0,0 +1,285 @@
|
||||
"""Support to select weekdays for use in automation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Self
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_EDITABLE,
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
SERVICE_RELOAD,
|
||||
WEEKDAYS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.helpers import collection, config_validation as cv
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
import homeassistant.helpers.service
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.helpers.typing import ConfigType, VolDictType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "input_weekday"
|
||||
|
||||
CONF_WEEKDAYS = "weekdays"
|
||||
|
||||
ATTR_WEEKDAYS = "weekdays"
|
||||
ATTR_WEEKDAY = "weekday"
|
||||
|
||||
SERVICE_SET_WEEKDAYS = "set_weekdays"
|
||||
SERVICE_ADD_WEEKDAY = "add_weekday"
|
||||
SERVICE_REMOVE_WEEKDAY = "remove_weekday"
|
||||
SERVICE_TOGGLE_WEEKDAY = "toggle_weekday"
|
||||
SERVICE_CLEAR = "clear"
|
||||
|
||||
STORAGE_KEY = DOMAIN
|
||||
STORAGE_VERSION = 1
|
||||
|
||||
STORAGE_FIELDS: VolDictType = {
|
||||
vol.Required(CONF_NAME): vol.All(str, vol.Length(min=1)),
|
||||
vol.Optional(CONF_WEEKDAYS, default=list): vol.All(
|
||||
cv.ensure_list, [vol.In(WEEKDAYS)]
|
||||
),
|
||||
vol.Optional(CONF_ICON): cv.icon,
|
||||
}
|
||||
|
||||
|
||||
def _cv_input_weekday(cfg: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Configure validation helper for input weekday (voluptuous)."""
|
||||
if CONF_WEEKDAYS in cfg:
|
||||
weekdays = cfg[CONF_WEEKDAYS]
|
||||
# Remove duplicates while preserving order
|
||||
cfg[CONF_WEEKDAYS] = list(dict.fromkeys(weekdays))
|
||||
return cfg
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: cv.schema_with_slug_keys(
|
||||
vol.All(
|
||||
{
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_WEEKDAYS): vol.All(
|
||||
cv.ensure_list, [vol.In(WEEKDAYS)]
|
||||
),
|
||||
vol.Optional(CONF_ICON): cv.icon,
|
||||
},
|
||||
_cv_input_weekday,
|
||||
)
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
RELOAD_SERVICE_SCHEMA = vol.Schema({})
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up an input weekday."""
|
||||
component = EntityComponent[InputWeekday](_LOGGER, DOMAIN, hass)
|
||||
|
||||
id_manager = collection.IDManager()
|
||||
|
||||
yaml_collection = collection.YamlCollection(
|
||||
logging.getLogger(f"{__name__}.yaml_collection"), id_manager
|
||||
)
|
||||
collection.sync_entity_lifecycle(
|
||||
hass, DOMAIN, DOMAIN, component, yaml_collection, InputWeekday
|
||||
)
|
||||
|
||||
storage_collection = InputWeekdayStorageCollection(
|
||||
Store(hass, STORAGE_VERSION, STORAGE_KEY),
|
||||
id_manager,
|
||||
)
|
||||
collection.sync_entity_lifecycle(
|
||||
hass, DOMAIN, DOMAIN, component, storage_collection, InputWeekday
|
||||
)
|
||||
|
||||
await yaml_collection.async_load(
|
||||
[{CONF_ID: id_, **cfg} for id_, cfg in config.get(DOMAIN, {}).items()]
|
||||
)
|
||||
await storage_collection.async_load()
|
||||
|
||||
collection.DictStorageCollectionWebsocket(
|
||||
storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
|
||||
).async_setup(hass)
|
||||
|
||||
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||
"""Reload yaml entities."""
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if conf is None:
|
||||
conf = {DOMAIN: {}}
|
||||
await yaml_collection.async_load(
|
||||
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
|
||||
)
|
||||
|
||||
homeassistant.helpers.service.async_register_admin_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_RELOAD,
|
||||
reload_service_handler,
|
||||
schema=RELOAD_SERVICE_SCHEMA,
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SET_WEEKDAYS,
|
||||
{vol.Required(ATTR_WEEKDAYS): vol.All(cv.ensure_list, [vol.In(WEEKDAYS)])},
|
||||
"async_set_weekdays",
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_ADD_WEEKDAY,
|
||||
{vol.Required(ATTR_WEEKDAY): vol.In(WEEKDAYS)},
|
||||
"async_add_weekday",
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_REMOVE_WEEKDAY,
|
||||
{vol.Required(ATTR_WEEKDAY): vol.In(WEEKDAYS)},
|
||||
"async_remove_weekday",
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_TOGGLE_WEEKDAY,
|
||||
{vol.Required(ATTR_WEEKDAY): vol.In(WEEKDAYS)},
|
||||
"async_toggle_weekday",
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_CLEAR,
|
||||
None,
|
||||
"async_clear",
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class InputWeekdayStorageCollection(collection.DictStorageCollection):
|
||||
"""Input weekday storage based collection."""
|
||||
|
||||
CREATE_UPDATE_SCHEMA = vol.Schema(vol.All(STORAGE_FIELDS, _cv_input_weekday))
|
||||
|
||||
async def _process_create_data(self, data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Validate the config is valid."""
|
||||
return self.CREATE_UPDATE_SCHEMA(data)
|
||||
|
||||
@callback
|
||||
def _get_suggested_id(self, info: dict[str, Any]) -> str:
|
||||
"""Suggest an ID based on the config."""
|
||||
return info[CONF_NAME]
|
||||
|
||||
async def _update_data(
|
||||
self, item: dict[str, Any], update_data: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Return a new updated data object."""
|
||||
update_data = self.CREATE_UPDATE_SCHEMA(update_data)
|
||||
return item | update_data
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class InputWeekday(collection.CollectionEntity, RestoreEntity):
|
||||
"""Representation of a weekday input."""
|
||||
|
||||
_unrecorded_attributes = frozenset({ATTR_EDITABLE})
|
||||
|
||||
_attr_should_poll = False
|
||||
editable: bool
|
||||
|
||||
def __init__(self, config: ConfigType) -> None:
|
||||
"""Initialize a weekday input."""
|
||||
self._config = config
|
||||
self._attr_weekdays = config.get(CONF_WEEKDAYS, [])
|
||||
self._attr_unique_id = config[CONF_ID]
|
||||
|
||||
@classmethod
|
||||
def from_storage(cls, config: ConfigType) -> Self:
|
||||
"""Return entity instance initialized from storage."""
|
||||
input_weekday = cls(config)
|
||||
input_weekday.editable = True
|
||||
return input_weekday
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, config: ConfigType) -> Self:
|
||||
"""Return entity instance initialized from yaml."""
|
||||
input_weekday = cls(config)
|
||||
input_weekday.entity_id = f"{DOMAIN}.{config[CONF_ID]}"
|
||||
input_weekday.editable = False
|
||||
return input_weekday
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return name of the weekday input."""
|
||||
return self._config.get(CONF_NAME) or self._config[CONF_ID]
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
"""Return the icon to be used for this entity."""
|
||||
return self._config.get(CONF_ICON)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Return the state of the entity."""
|
||||
# Return a comma-separated string of selected weekdays
|
||||
return ",".join(self._attr_weekdays) if self._attr_weekdays else ""
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of the entity."""
|
||||
return {
|
||||
ATTR_WEEKDAYS: self._attr_weekdays,
|
||||
ATTR_EDITABLE: self.editable,
|
||||
}
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call when entity about to be added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
# Restore previous state if no initial weekdays were provided
|
||||
if self._config.get(CONF_WEEKDAYS) is not None:
|
||||
return
|
||||
|
||||
state = await self.async_get_last_state()
|
||||
if state is not None and ATTR_WEEKDAYS in state.attributes:
|
||||
self._attr_weekdays = state.attributes[ATTR_WEEKDAYS]
|
||||
|
||||
async def async_set_weekdays(self, weekdays: list[str]) -> None:
|
||||
"""Set the selected weekdays."""
|
||||
# Remove duplicates while preserving order
|
||||
self._attr_weekdays = list(dict.fromkeys(weekdays))
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_add_weekday(self, weekday: str) -> None:
|
||||
"""Add a weekday to the selection."""
|
||||
if weekday not in self._attr_weekdays:
|
||||
self._attr_weekdays.append(weekday)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_remove_weekday(self, weekday: str) -> None:
|
||||
"""Remove a weekday from the selection."""
|
||||
if weekday in self._attr_weekdays:
|
||||
self._attr_weekdays.remove(weekday)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_toggle_weekday(self, weekday: str) -> None:
|
||||
"""Toggle a weekday in the selection."""
|
||||
if weekday in self._attr_weekdays:
|
||||
self._attr_weekdays.remove(weekday)
|
||||
else:
|
||||
self._attr_weekdays.append(weekday)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_clear(self) -> None:
|
||||
"""Clear all selected weekdays."""
|
||||
self._attr_weekdays = []
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_update_config(self, config: ConfigType) -> None:
|
||||
"""Handle when the config is updated."""
|
||||
self._config = config
|
||||
self._attr_weekdays = config.get(CONF_WEEKDAYS, [])
|
||||
self.async_write_ha_state()
|
||||
29
homeassistant/components/input_weekday/icons.json
Normal file
29
homeassistant/components/input_weekday/icons.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"entity": {
|
||||
"input_weekday": {
|
||||
"default": {
|
||||
"default": "mdi:calendar-week"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"set_weekdays": {
|
||||
"service": "mdi:calendar-edit"
|
||||
},
|
||||
"add_weekday": {
|
||||
"service": "mdi:calendar-plus"
|
||||
},
|
||||
"remove_weekday": {
|
||||
"service": "mdi:calendar-minus"
|
||||
},
|
||||
"toggle_weekday": {
|
||||
"service": "mdi:calendar-check"
|
||||
},
|
||||
"clear": {
|
||||
"service": "mdi:calendar-remove"
|
||||
},
|
||||
"reload": {
|
||||
"service": "mdi:reload"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
homeassistant/components/input_weekday/manifest.json
Normal file
8
homeassistant/components/input_weekday/manifest.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "input_weekday",
|
||||
"name": "Input Weekday",
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/input_weekday",
|
||||
"integration_type": "helper",
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
42
homeassistant/components/input_weekday/reproduce_state.py
Normal file
42
homeassistant/components/input_weekday/reproduce_state.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Reproduce an Input Weekday state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import Context, HomeAssistant, State
|
||||
|
||||
from . import ATTR_WEEKDAYS, DOMAIN, SERVICE_SET_WEEKDAYS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_reproduce_states(
|
||||
hass: HomeAssistant,
|
||||
states: list[State],
|
||||
*,
|
||||
context: Context | None = None,
|
||||
reproduce_options: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Reproduce Input Weekday states."""
|
||||
for state in states:
|
||||
if ATTR_WEEKDAYS not in state.attributes:
|
||||
_LOGGER.warning(
|
||||
"Unable to reproduce state for %s: %s attribute is missing",
|
||||
state.entity_id,
|
||||
ATTR_WEEKDAYS,
|
||||
)
|
||||
continue
|
||||
|
||||
weekdays = state.attributes[ATTR_WEEKDAYS]
|
||||
|
||||
service_data = {
|
||||
ATTR_ENTITY_ID: state.entity_id,
|
||||
ATTR_WEEKDAYS: weekdays,
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, SERVICE_SET_WEEKDAYS, service_data, context=context, blocking=True
|
||||
)
|
||||
115
homeassistant/components/input_weekday/services.yaml
Normal file
115
homeassistant/components/input_weekday/services.yaml
Normal file
@@ -0,0 +1,115 @@
|
||||
set_weekdays:
|
||||
target:
|
||||
entity:
|
||||
domain: input_weekday
|
||||
fields:
|
||||
weekdays:
|
||||
required: true
|
||||
example: '["mon", "wed", "fri"]'
|
||||
selector:
|
||||
select:
|
||||
multiple: true
|
||||
mode: list
|
||||
options:
|
||||
- value: mon
|
||||
label: Monday
|
||||
- value: tue
|
||||
label: Tuesday
|
||||
- value: wed
|
||||
label: Wednesday
|
||||
- value: thu
|
||||
label: Thursday
|
||||
- value: fri
|
||||
label: Friday
|
||||
- value: sat
|
||||
label: Saturday
|
||||
- value: sun
|
||||
label: Sunday
|
||||
|
||||
add_weekday:
|
||||
target:
|
||||
entity:
|
||||
domain: input_weekday
|
||||
fields:
|
||||
weekday:
|
||||
required: true
|
||||
example: mon
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
options:
|
||||
- value: mon
|
||||
label: Monday
|
||||
- value: tue
|
||||
label: Tuesday
|
||||
- value: wed
|
||||
label: Wednesday
|
||||
- value: thu
|
||||
label: Thursday
|
||||
- value: fri
|
||||
label: Friday
|
||||
- value: sat
|
||||
label: Saturday
|
||||
- value: sun
|
||||
label: Sunday
|
||||
|
||||
remove_weekday:
|
||||
target:
|
||||
entity:
|
||||
domain: input_weekday
|
||||
fields:
|
||||
weekday:
|
||||
required: true
|
||||
example: mon
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
options:
|
||||
- value: mon
|
||||
label: Monday
|
||||
- value: tue
|
||||
label: Tuesday
|
||||
- value: wed
|
||||
label: Wednesday
|
||||
- value: thu
|
||||
label: Thursday
|
||||
- value: fri
|
||||
label: Friday
|
||||
- value: sat
|
||||
label: Saturday
|
||||
- value: sun
|
||||
label: Sunday
|
||||
|
||||
toggle_weekday:
|
||||
target:
|
||||
entity:
|
||||
domain: input_weekday
|
||||
fields:
|
||||
weekday:
|
||||
required: true
|
||||
example: mon
|
||||
selector:
|
||||
select:
|
||||
mode: dropdown
|
||||
options:
|
||||
- value: mon
|
||||
label: Monday
|
||||
- value: tue
|
||||
label: Tuesday
|
||||
- value: wed
|
||||
label: Wednesday
|
||||
- value: thu
|
||||
label: Thursday
|
||||
- value: fri
|
||||
label: Friday
|
||||
- value: sat
|
||||
label: Saturday
|
||||
- value: sun
|
||||
label: Sunday
|
||||
|
||||
clear:
|
||||
target:
|
||||
entity:
|
||||
domain: input_weekday
|
||||
|
||||
reload:
|
||||
70
homeassistant/components/input_weekday/strings.json
Normal file
70
homeassistant/components/input_weekday/strings.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"title": "Input Weekday",
|
||||
"entity_component": {
|
||||
"_": {
|
||||
"name": "[%key:component::input_weekday::title%]",
|
||||
"state_attributes": {
|
||||
"weekdays": {
|
||||
"name": "Weekdays"
|
||||
},
|
||||
"editable": {
|
||||
"name": "[%key:common::generic::ui_managed%]",
|
||||
"state": {
|
||||
"true": "[%key:common::state::yes%]",
|
||||
"false": "[%key:common::state::no%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"set_weekdays": {
|
||||
"name": "Set weekdays",
|
||||
"description": "Sets the selected weekdays.",
|
||||
"fields": {
|
||||
"weekdays": {
|
||||
"name": "Weekdays",
|
||||
"description": "List of weekdays to select."
|
||||
}
|
||||
}
|
||||
},
|
||||
"add_weekday": {
|
||||
"name": "Add weekday",
|
||||
"description": "Adds a weekday to the selection.",
|
||||
"fields": {
|
||||
"weekday": {
|
||||
"name": "Weekday",
|
||||
"description": "Weekday to add."
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove_weekday": {
|
||||
"name": "Remove weekday",
|
||||
"description": "Removes a weekday from the selection.",
|
||||
"fields": {
|
||||
"weekday": {
|
||||
"name": "Weekday",
|
||||
"description": "Weekday to remove."
|
||||
}
|
||||
}
|
||||
},
|
||||
"toggle_weekday": {
|
||||
"name": "Toggle weekday",
|
||||
"description": "Toggles a weekday in the selection.",
|
||||
"fields": {
|
||||
"weekday": {
|
||||
"name": "Weekday",
|
||||
"description": "Weekday to toggle."
|
||||
}
|
||||
}
|
||||
},
|
||||
"clear": {
|
||||
"name": "Clear",
|
||||
"description": "Clears all selected weekdays."
|
||||
},
|
||||
"reload": {
|
||||
"name": "[%key:common::action::reload%]",
|
||||
"description": "Reloads helpers from the YAML-configuration."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7922,6 +7922,10 @@
|
||||
"integration_type": "helper",
|
||||
"config_flow": false
|
||||
},
|
||||
"input_weekday": {
|
||||
"integration_type": "helper",
|
||||
"config_flow": false
|
||||
},
|
||||
"integration": {
|
||||
"integration_type": "helper",
|
||||
"config_flow": true,
|
||||
@@ -8021,6 +8025,7 @@
|
||||
"input_number",
|
||||
"input_select",
|
||||
"input_text",
|
||||
"input_weekday",
|
||||
"integration",
|
||||
"irm_kmi",
|
||||
"islamic_prayer_times",
|
||||
|
||||
@@ -954,11 +954,25 @@ def time(
|
||||
if weekday is not None:
|
||||
now_weekday = WEEKDAYS[now.weekday()]
|
||||
|
||||
condition_trace_update_result(weekday=weekday, now_weekday=now_weekday)
|
||||
if (
|
||||
isinstance(weekday, str) and weekday != now_weekday
|
||||
) or now_weekday not in weekday:
|
||||
return False
|
||||
# Check if weekday is an entity_id
|
||||
if isinstance(weekday, str) and weekday.startswith("input_weekday."):
|
||||
if (weekday_state := hass.states.get(weekday)) is None:
|
||||
condition_trace_update_result(weekday=weekday, now_weekday=now_weekday)
|
||||
return False
|
||||
entity_weekdays = weekday_state.attributes.get("weekdays", [])
|
||||
condition_trace_update_result(
|
||||
weekday=weekday,
|
||||
now_weekday=now_weekday,
|
||||
entity_weekdays=entity_weekdays,
|
||||
)
|
||||
if now_weekday not in entity_weekdays:
|
||||
return False
|
||||
else:
|
||||
condition_trace_update_result(weekday=weekday, now_weekday=now_weekday)
|
||||
if (
|
||||
isinstance(weekday, str) and weekday != now_weekday
|
||||
) or now_weekday not in weekday:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -843,7 +843,10 @@ def time_zone(value: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
weekdays = vol.All(ensure_list, [vol.In(WEEKDAYS)])
|
||||
weekdays = vol.Any(
|
||||
vol.All(ensure_list, [vol.In(WEEKDAYS)]),
|
||||
entity_domain(["input_weekday"]),
|
||||
)
|
||||
|
||||
|
||||
def socket_timeout(value: Any | None) -> object:
|
||||
|
||||
@@ -91,6 +91,7 @@ NO_IOT_CLASS = [
|
||||
"input_number",
|
||||
"input_select",
|
||||
"input_text",
|
||||
"input_weekday",
|
||||
"intent_script",
|
||||
"intent",
|
||||
"logbook",
|
||||
|
||||
@@ -2214,6 +2214,7 @@ NO_QUALITY_SCALE = [
|
||||
"input_number",
|
||||
"input_select",
|
||||
"input_text",
|
||||
"input_weekday",
|
||||
"intent_script",
|
||||
"intent",
|
||||
"logbook",
|
||||
|
||||
@@ -1061,6 +1061,14 @@ def test_weekday_validation() -> None:
|
||||
}
|
||||
time.TRIGGER_SCHEMA(valid_config)
|
||||
|
||||
# Valid input_weekday entity
|
||||
valid_config = {
|
||||
"platform": "time",
|
||||
"at": "5:00:00",
|
||||
"weekday": "input_weekday.workdays",
|
||||
}
|
||||
time.TRIGGER_SCHEMA(valid_config)
|
||||
|
||||
# Invalid weekday
|
||||
invalid_config = {"platform": "time", "at": "5:00:00", "weekday": "invalid"}
|
||||
with pytest.raises(vol.Invalid):
|
||||
@@ -1074,3 +1082,176 @@ def test_weekday_validation() -> None:
|
||||
}
|
||||
with pytest.raises(vol.Invalid):
|
||||
time.TRIGGER_SCHEMA(invalid_config)
|
||||
|
||||
# Invalid entity domain
|
||||
invalid_config = {
|
||||
"platform": "time",
|
||||
"at": "5:00:00",
|
||||
"weekday": "input_boolean.my_bool",
|
||||
}
|
||||
with pytest.raises(vol.Invalid):
|
||||
time.TRIGGER_SCHEMA(invalid_config)
|
||||
|
||||
|
||||
async def test_if_fires_using_weekday_input_weekday_entity(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
service_calls: list[ServiceCall],
|
||||
) -> None:
|
||||
"""Test for firing on weekday using input_weekday entity."""
|
||||
# Setup input_weekday helper with Mon, Tue, Wed
|
||||
await async_setup_component(
|
||||
hass,
|
||||
"input_weekday",
|
||||
{
|
||||
"input_weekday": {
|
||||
"workdays": {
|
||||
"name": "Work Days",
|
||||
"weekdays": ["mon", "tue", "wed"],
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Freeze time to Monday, January 2, 2023 at 5:00:00
|
||||
monday_trigger = dt_util.as_utc(datetime(2023, 1, 2, 5, 0, 0, 0))
|
||||
freezer.move_to(monday_trigger)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"trigger": {
|
||||
"platform": "time",
|
||||
"at": "5:00:00",
|
||||
"weekday": "input_weekday.workdays",
|
||||
},
|
||||
"action": {
|
||||
"service": "test.automation",
|
||||
"data_template": {
|
||||
"some": "{{ trigger.platform }} - {{ trigger.now.strftime('%A') }}",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fire on Monday - should trigger (Monday is in workdays)
|
||||
async_fire_time_changed(hass, monday_trigger + timedelta(seconds=1))
|
||||
await hass.async_block_till_done()
|
||||
automation_calls = [call for call in service_calls if call.domain == "test"]
|
||||
assert len(automation_calls) == 1
|
||||
assert "Monday" in automation_calls[0].data["some"]
|
||||
|
||||
# Fire on Tuesday - should trigger (Tuesday is in workdays)
|
||||
tuesday_trigger = dt_util.as_utc(datetime(2023, 1, 3, 5, 0, 0, 0))
|
||||
async_fire_time_changed(hass, tuesday_trigger)
|
||||
await hass.async_block_till_done()
|
||||
automation_calls = [call for call in service_calls if call.domain == "test"]
|
||||
assert len(automation_calls) == 2
|
||||
assert "Tuesday" in automation_calls[1].data["some"]
|
||||
|
||||
# Fire on Thursday - should not trigger (Thursday is not in workdays)
|
||||
thursday_trigger = dt_util.as_utc(datetime(2023, 1, 5, 5, 0, 0, 0))
|
||||
async_fire_time_changed(hass, thursday_trigger)
|
||||
await hass.async_block_till_done()
|
||||
automation_calls = [call for call in service_calls if call.domain == "test"]
|
||||
assert len(automation_calls) == 2
|
||||
|
||||
# Fire on Saturday - should not trigger (Saturday is not in workdays)
|
||||
saturday_trigger = dt_util.as_utc(datetime(2023, 1, 7, 5, 0, 0, 0))
|
||||
async_fire_time_changed(hass, saturday_trigger)
|
||||
await hass.async_block_till_done()
|
||||
automation_calls = [call for call in service_calls if call.domain == "test"]
|
||||
assert len(automation_calls) == 2
|
||||
|
||||
|
||||
async def test_if_action_weekday_input_weekday_entity(
|
||||
hass: HomeAssistant, service_calls: list[ServiceCall]
|
||||
) -> None:
|
||||
"""Test time condition with input_weekday entity."""
|
||||
# Setup input_weekday helper with Sat, Sun
|
||||
await async_setup_component(
|
||||
hass,
|
||||
"input_weekday",
|
||||
{
|
||||
"input_weekday": {
|
||||
"weekend": {"name": "Weekend Days", "weekdays": ["sat", "sun"]}
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"trigger": {"platform": "event", "event_type": "test_event"},
|
||||
"condition": {"condition": "time", "weekday": "input_weekday.weekend"},
|
||||
"action": {"service": "test.automation"},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
days_past_monday = dt_util.now().weekday()
|
||||
monday = dt_util.now() - timedelta(days=days_past_monday)
|
||||
saturday = monday + timedelta(days=5)
|
||||
sunday = saturday + timedelta(days=1)
|
||||
|
||||
# Test on Monday - should not trigger (not in weekend)
|
||||
with patch("homeassistant.helpers.condition.dt_util.now", return_value=monday):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 0
|
||||
|
||||
# Test on Saturday - should trigger
|
||||
with patch("homeassistant.helpers.condition.dt_util.now", return_value=saturday):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 1
|
||||
|
||||
# Test on Sunday - should trigger
|
||||
with patch("homeassistant.helpers.condition.dt_util.now", return_value=sunday):
|
||||
hass.bus.async_fire("test_event")
|
||||
await hass.async_block_till_done()
|
||||
assert len(service_calls) == 2
|
||||
|
||||
|
||||
async def test_if_fires_weekday_entity_unavailable(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
service_calls: list[ServiceCall],
|
||||
) -> None:
|
||||
"""Test that trigger does not fire when input_weekday entity is unavailable."""
|
||||
# Freeze time to Monday, January 2, 2023 at 5:00:00
|
||||
monday_trigger = dt_util.as_utc(datetime(2023, 1, 2, 5, 0, 0, 0))
|
||||
freezer.move_to(monday_trigger)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"trigger": {
|
||||
"platform": "time",
|
||||
"at": "5:00:00",
|
||||
"weekday": "input_weekday.nonexistent",
|
||||
},
|
||||
"action": {
|
||||
"service": "test.automation",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fire on Monday - should not trigger (entity doesn't exist)
|
||||
async_fire_time_changed(hass, monday_trigger + timedelta(seconds=1))
|
||||
await hass.async_block_till_done()
|
||||
automation_calls = [call for call in service_calls if call.domain == "test"]
|
||||
assert len(automation_calls) == 0
|
||||
|
||||
1
tests/components/input_weekday/__init__.py
Normal file
1
tests/components/input_weekday/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Tests for the Input Weekday component."""
|
||||
518
tests/components/input_weekday/test_init.py
Normal file
518
tests/components/input_weekday/test_init.py
Normal file
@@ -0,0 +1,518 @@
|
||||
"""Tests for the Input Weekday component."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.input_weekday import (
|
||||
ATTR_WEEKDAY,
|
||||
ATTR_WEEKDAYS,
|
||||
DOMAIN,
|
||||
SERVICE_ADD_WEEKDAY,
|
||||
SERVICE_CLEAR,
|
||||
SERVICE_REMOVE_WEEKDAY,
|
||||
SERVICE_SET_WEEKDAYS,
|
||||
SERVICE_TOGGLE_WEEKDAY,
|
||||
STORAGE_VERSION,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_EDITABLE,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import mock_restore_cache
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def storage_setup(hass: HomeAssistant, hass_storage: dict[str, Any]):
|
||||
"""Storage setup."""
|
||||
|
||||
async def _storage(items=None, config=None):
|
||||
if items is None:
|
||||
hass_storage[DOMAIN] = {
|
||||
"key": DOMAIN,
|
||||
"version": STORAGE_VERSION,
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "from_storage",
|
||||
"name": "from storage",
|
||||
"weekdays": ["mon", "wed", "fri"],
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
else:
|
||||
hass_storage[DOMAIN] = {
|
||||
"key": DOMAIN,
|
||||
"version": STORAGE_VERSION,
|
||||
"data": {"items": items},
|
||||
}
|
||||
if config is None:
|
||||
config = {DOMAIN: {}}
|
||||
return await async_setup_component(hass, DOMAIN, config)
|
||||
|
||||
return _storage
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_config",
|
||||
[
|
||||
None,
|
||||
{"name with space": None},
|
||||
{"bad_weekdays": {"weekdays": ["invalid"]}},
|
||||
],
|
||||
)
|
||||
async def test_config(hass: HomeAssistant, invalid_config) -> None:
|
||||
"""Test config."""
|
||||
assert not await async_setup_component(hass, DOMAIN, {DOMAIN: invalid_config})
|
||||
|
||||
|
||||
async def test_set_weekdays(hass: HomeAssistant) -> None:
|
||||
"""Test set_weekdays service."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon", "tue"]}}},
|
||||
)
|
||||
entity_id = "input_weekday.test_1"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "mon,tue"
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "tue"]
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_WEEKDAYS,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAYS: ["wed", "thu", "fri"]},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "wed,thu,fri"
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["wed", "thu", "fri"]
|
||||
|
||||
|
||||
async def test_set_weekdays_removes_duplicates(hass: HomeAssistant) -> None:
|
||||
"""Test set_weekdays removes duplicate weekdays."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": []}}},
|
||||
)
|
||||
entity_id = "input_weekday.test_1"
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SET_WEEKDAYS,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAYS: ["mon", "tue", "mon", "wed"]},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "tue", "wed"]
|
||||
|
||||
|
||||
async def test_add_weekday(hass: HomeAssistant) -> None:
|
||||
"""Test add_weekday service."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon"]}}},
|
||||
)
|
||||
entity_id = "input_weekday.test_1"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon"]
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_WEEKDAY,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAY: "wed"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "wed"]
|
||||
|
||||
# Adding duplicate should not add it again
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_ADD_WEEKDAY,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAY: "mon"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "wed"]
|
||||
|
||||
|
||||
async def test_remove_weekday(hass: HomeAssistant) -> None:
|
||||
"""Test remove_weekday service."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon", "wed", "fri"]}}},
|
||||
)
|
||||
entity_id = "input_weekday.test_1"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "wed", "fri"]
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REMOVE_WEEKDAY,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAY: "wed"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "fri"]
|
||||
|
||||
# Removing non-existent weekday should not error
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_REMOVE_WEEKDAY,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAY: "wed"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "fri"]
|
||||
|
||||
|
||||
async def test_toggle_weekday(hass: HomeAssistant) -> None:
|
||||
"""Test toggle_weekday service."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon"]}}},
|
||||
)
|
||||
entity_id = "input_weekday.test_1"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon"]
|
||||
|
||||
# Toggle off (remove)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TOGGLE_WEEKDAY,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAY: "mon"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == []
|
||||
|
||||
# Toggle on (add)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TOGGLE_WEEKDAY,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_WEEKDAY: "tue"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["tue"]
|
||||
|
||||
|
||||
async def test_clear(hass: HomeAssistant) -> None:
|
||||
"""Test clear service."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon", "wed", "fri"]}}},
|
||||
)
|
||||
entity_id = "input_weekday.test_1"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "wed", "fri"]
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_CLEAR,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == ""
|
||||
assert state.attributes[ATTR_WEEKDAYS] == []
|
||||
|
||||
|
||||
async def test_config_with_name(hass: HomeAssistant) -> None:
|
||||
"""Test configuration with name."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"name": "Test Weekday", "weekdays": ["sat", "sun"]}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test_1")
|
||||
assert state is not None
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "Test Weekday"
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["sat", "sun"]
|
||||
|
||||
|
||||
async def test_empty_weekdays(hass: HomeAssistant) -> None:
|
||||
"""Test empty weekdays configuration."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": []}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test_1")
|
||||
assert state is not None
|
||||
assert state.state == ""
|
||||
assert state.attributes[ATTR_WEEKDAYS] == []
|
||||
|
||||
|
||||
async def test_default_weekdays(hass: HomeAssistant) -> None:
|
||||
"""Test default weekdays (empty list)."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test_1")
|
||||
assert state is not None
|
||||
assert state.state == ""
|
||||
assert state.attributes[ATTR_WEEKDAYS] == []
|
||||
|
||||
|
||||
async def test_config_removes_duplicates(hass: HomeAssistant) -> None:
|
||||
"""Test that configuration removes duplicate weekdays."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon", "tue", "mon", "wed"]}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test_1")
|
||||
assert state is not None
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "tue", "wed"]
|
||||
|
||||
|
||||
async def test_reload(hass: HomeAssistant) -> None:
|
||||
"""Test reload service."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon"]}}},
|
||||
)
|
||||
|
||||
state_1 = hass.states.get("input_weekday.test_1")
|
||||
state_2 = hass.states.get("input_weekday.test_2")
|
||||
|
||||
assert state_1 is not None
|
||||
assert state_2 is None
|
||||
assert state_1.attributes[ATTR_WEEKDAYS] == ["mon"]
|
||||
|
||||
with patch(
|
||||
"homeassistant.config.load_yaml_config_file",
|
||||
return_value={
|
||||
DOMAIN: {
|
||||
"test_2": {"weekdays": ["tue", "thu"]},
|
||||
}
|
||||
},
|
||||
):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_RELOAD,
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state_1 = hass.states.get("input_weekday.test_1")
|
||||
state_2 = hass.states.get("input_weekday.test_2")
|
||||
|
||||
assert state_1 is None
|
||||
assert state_2 is not None
|
||||
assert state_2.attributes[ATTR_WEEKDAYS] == ["tue", "thu"]
|
||||
|
||||
|
||||
async def test_state_restoration(hass: HomeAssistant) -> None:
|
||||
"""Test state restoration."""
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"input_weekday.test_1",
|
||||
"mon,wed,fri",
|
||||
{ATTR_WEEKDAYS: ["mon", "wed", "fri"]},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
hass.state = "starting"
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test_1")
|
||||
assert state
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "wed", "fri"]
|
||||
|
||||
|
||||
async def test_state_restoration_with_initial(hass: HomeAssistant) -> None:
|
||||
"""Test state restoration with initial value - should prefer initial."""
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"input_weekday.test_1",
|
||||
"mon,wed,fri",
|
||||
{ATTR_WEEKDAYS: ["mon", "wed", "fri"]},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
hass.state = "starting"
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["sat", "sun"]}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test_1")
|
||||
assert state
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["sat", "sun"]
|
||||
|
||||
|
||||
async def test_storage(hass: HomeAssistant, storage_setup) -> None:
|
||||
"""Test storage."""
|
||||
assert await storage_setup()
|
||||
state = hass.states.get("input_weekday.from_storage")
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "wed", "fri"]
|
||||
assert state.attributes[ATTR_EDITABLE]
|
||||
|
||||
|
||||
async def test_editable_state_attribute(hass: HomeAssistant) -> None:
|
||||
"""Test editable attribute."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{DOMAIN: {"test_1": {"weekdays": ["mon"]}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test_1")
|
||||
assert state.attributes[ATTR_EDITABLE] is False
|
||||
|
||||
|
||||
async def test_websocket_create(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test create via websocket."""
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": f"{DOMAIN}/create",
|
||||
"name": "My Weekday",
|
||||
"weekdays": ["mon", "fri"],
|
||||
}
|
||||
)
|
||||
resp = await client.receive_json()
|
||||
assert resp["success"]
|
||||
|
||||
state = hass.states.get("input_weekday.my_weekday")
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "fri"]
|
||||
|
||||
|
||||
async def test_websocket_update(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test update via websocket."""
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": f"{DOMAIN}/create",
|
||||
"name": "My Weekday",
|
||||
"weekdays": ["mon"],
|
||||
}
|
||||
)
|
||||
resp = await client.receive_json()
|
||||
assert resp["success"]
|
||||
|
||||
state = hass.states.get("input_weekday.my_weekday")
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon"]
|
||||
|
||||
entity_entry = entity_registry.async_get("input_weekday.my_weekday")
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 2,
|
||||
"type": f"{DOMAIN}/update",
|
||||
f"{DOMAIN}_id": entity_entry.unique_id,
|
||||
"weekdays": ["tue", "wed"],
|
||||
"name": "Updated Weekday",
|
||||
}
|
||||
)
|
||||
resp = await client.receive_json()
|
||||
assert resp["success"]
|
||||
|
||||
state = hass.states.get("input_weekday.my_weekday")
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["tue", "wed"]
|
||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "Updated Weekday"
|
||||
|
||||
|
||||
async def test_websocket_delete(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test delete via websocket."""
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": f"{DOMAIN}/create",
|
||||
"name": "My Weekday",
|
||||
"weekdays": ["mon"],
|
||||
}
|
||||
)
|
||||
resp = await client.receive_json()
|
||||
assert resp["success"]
|
||||
|
||||
state = hass.states.get("input_weekday.my_weekday")
|
||||
assert state is not None
|
||||
|
||||
entity_entry = entity_registry.async_get("input_weekday.my_weekday")
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 2,
|
||||
"type": f"{DOMAIN}/delete",
|
||||
f"{DOMAIN}_id": entity_entry.unique_id,
|
||||
}
|
||||
)
|
||||
resp = await client.receive_json()
|
||||
assert resp["success"]
|
||||
|
||||
state = hass.states.get("input_weekday.my_weekday")
|
||||
assert state is None
|
||||
37
tests/components/input_weekday/test_recorder.py
Normal file
37
tests/components/input_weekday/test_recorder.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Tests for the Input Weekday recorder."""
|
||||
|
||||
from homeassistant.components.input_weekday import ATTR_EDITABLE, ATTR_WEEKDAYS
|
||||
from homeassistant.components.recorder import Recorder
|
||||
from homeassistant.components.recorder.history import get_significant_states
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.components.recorder.common import async_wait_recording_done
|
||||
|
||||
|
||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||
"""Test that certain attributes are excluded."""
|
||||
now = dt_util.utcnow()
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"input_weekday",
|
||||
{"input_weekday": {"test": {"weekdays": ["mon", "wed"]}}},
|
||||
)
|
||||
|
||||
state = hass.states.get("input_weekday.test")
|
||||
assert state.attributes[ATTR_WEEKDAYS] == ["mon", "wed"]
|
||||
assert state.attributes[ATTR_EDITABLE] is False
|
||||
|
||||
await async_wait_recording_done(hass)
|
||||
|
||||
states = await hass.async_add_executor_job(
|
||||
get_significant_states, hass, now, None, ["input_weekday.test"]
|
||||
)
|
||||
assert len(states) == 1
|
||||
for entity_states in states.values():
|
||||
for state in entity_states:
|
||||
assert ATTR_WEEKDAYS in state.attributes
|
||||
assert ATTR_EDITABLE not in state.attributes
|
||||
assert ATTR_FRIENDLY_NAME in state.attributes
|
||||
59
tests/components/input_weekday/test_reproduce_state.py
Normal file
59
tests/components/input_weekday/test_reproduce_state.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Test reproduce state for Input Weekday."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.input_weekday import ATTR_WEEKDAYS, DOMAIN
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.state import async_reproduce_state
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def setup_component(hass: HomeAssistant):
|
||||
"""Set up component."""
|
||||
assert await async_setup_component(
|
||||
hass, DOMAIN, {DOMAIN: {"test_weekday": {"weekdays": []}}}
|
||||
)
|
||||
|
||||
|
||||
async def test_reproduce_weekday(hass: HomeAssistant) -> None:
|
||||
"""Test reproduce weekday."""
|
||||
calls = async_mock_service(hass, DOMAIN, "set_weekdays")
|
||||
|
||||
await async_reproduce_state(
|
||||
hass,
|
||||
[
|
||||
State(
|
||||
"input_weekday.test_weekday",
|
||||
"mon,wed,fri",
|
||||
{ATTR_WEEKDAYS: ["mon", "wed", "fri"]},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data == {
|
||||
"entity_id": "input_weekday.test_weekday",
|
||||
ATTR_WEEKDAYS: ["mon", "wed", "fri"],
|
||||
}
|
||||
|
||||
|
||||
async def test_reproduce_weekday_missing_attribute(
|
||||
hass: HomeAssistant, setup_component, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test reproduce weekday with missing weekdays attribute."""
|
||||
calls = async_mock_service(hass, DOMAIN, "set_weekdays")
|
||||
|
||||
await async_reproduce_state(
|
||||
hass,
|
||||
[State("input_weekday.test_weekday", "mon,wed")],
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(calls) == 0
|
||||
assert "weekdays attribute is missing" in caplog.text
|
||||
Reference in New Issue
Block a user