mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add Holiday integration (#103795)
* Add Holiday integration * Localize holiday names * Changes based on review feedback * Add tests * Add device info * Bump holidays to 0.36 * Default to Home Assistant country setting * Update homeassistant/components/holiday/calendar.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/holiday/calendar.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/holiday/config_flow.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * black * Move time * Stop creating duplicate holiday calendars * Set default language using python-holiday * Use common translation * Set _attr_name to None to fix friendly name * Fix location * Update homeassistant/components/holiday/__init__.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/holiday/calendar.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/holiday/calendar.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update tests/components/holiday/test_init.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * cleanup * Set up the integration and test the state * Test that configuring more than one instance is rejected * Set default_language to user's language, fallback to country's default language * Improve tests * Update homeassistant/components/holiday/calendar.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Cleanup * Add next year so we don't run out * Update tests/components/holiday/test_init.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Cleanup * Set default language in `__init__` * Add strict typing * Change default language: HA's language `en` is `en_US` in holidays, apart from Canada * CONF_PROVINCE can be None * Fix test * Fix default_language * Refactor tests * Province can be None * Add test for translated title * Address feedback * Address feedback * Change test to use service call * Address feedback * Apply suggestions from code review Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Changes based on review feedback * Update homeassistant/components/holiday/calendar.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/holiday/calendar.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Add a test if next event is missing * Rebase * Set device to service * Remove not needed translation key --------- Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
parent
67784def13
commit
244edb488b
@ -152,6 +152,7 @@ homeassistant.components.hardkernel.*
|
||||
homeassistant.components.hardware.*
|
||||
homeassistant.components.here_travel_time.*
|
||||
homeassistant.components.history.*
|
||||
homeassistant.components.holiday.*
|
||||
homeassistant.components.homeassistant.exposed_entities
|
||||
homeassistant.components.homeassistant.triggers.event
|
||||
homeassistant.components.homeassistant_alerts.*
|
||||
|
@ -522,6 +522,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/hive/ @Rendili @KJonline
|
||||
/homeassistant/components/hlk_sw16/ @jameshilliard
|
||||
/tests/components/hlk_sw16/ @jameshilliard
|
||||
/homeassistant/components/holiday/ @jrieger
|
||||
/tests/components/holiday/ @jrieger
|
||||
/homeassistant/components/home_connect/ @DavidMStraub
|
||||
/tests/components/home_connect/ @DavidMStraub
|
||||
/homeassistant/components/home_plus_control/ @chemaaa
|
||||
|
20
homeassistant/components/holiday/__init__.py
Normal file
20
homeassistant/components/holiday/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""The Holiday integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CALENDAR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Holiday 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 a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
134
homeassistant/components/holiday/calendar.py
Normal file
134
homeassistant/components/holiday/calendar.py
Normal file
@ -0,0 +1,134 @@
|
||||
"""Holiday Calendar."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from holidays import HolidayBase, country_holidays
|
||||
|
||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_COUNTRY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import CONF_PROVINCE, DOMAIN
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Holiday Calendar config entry."""
|
||||
country: str = config_entry.data[CONF_COUNTRY]
|
||||
province: str | None = config_entry.data.get(CONF_PROVINCE)
|
||||
language = hass.config.language
|
||||
|
||||
obj_holidays = country_holidays(
|
||||
country,
|
||||
subdiv=province,
|
||||
years={dt_util.now().year, dt_util.now().year + 1},
|
||||
language=language,
|
||||
)
|
||||
if language == "en":
|
||||
for lang in obj_holidays.supported_languages:
|
||||
if lang.startswith("en"):
|
||||
obj_holidays = country_holidays(
|
||||
country,
|
||||
subdiv=province,
|
||||
years={dt_util.now().year, dt_util.now().year + 1},
|
||||
language=lang,
|
||||
)
|
||||
language = lang
|
||||
break
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
HolidayCalendarEntity(
|
||||
config_entry.title,
|
||||
country,
|
||||
province,
|
||||
language,
|
||||
obj_holidays,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
],
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class HolidayCalendarEntity(CalendarEntity):
|
||||
"""Representation of a Holiday Calendar element."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
country: str,
|
||||
province: str | None,
|
||||
language: str,
|
||||
obj_holidays: HolidayBase,
|
||||
unique_id: str,
|
||||
) -> None:
|
||||
"""Initialize HolidayCalendarEntity."""
|
||||
self._country = country
|
||||
self._province = province
|
||||
self._location = name
|
||||
self._language = language
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, unique_id)},
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
name=name,
|
||||
)
|
||||
self._obj_holidays = obj_holidays
|
||||
|
||||
@property
|
||||
def event(self) -> CalendarEvent | None:
|
||||
"""Return the next upcoming event."""
|
||||
next_holiday = None
|
||||
for holiday_date, holiday_name in sorted(
|
||||
self._obj_holidays.items(), key=lambda x: x[0]
|
||||
):
|
||||
if holiday_date >= dt_util.now().date():
|
||||
next_holiday = (holiday_date, holiday_name)
|
||||
break
|
||||
|
||||
if next_holiday is None:
|
||||
return None
|
||||
|
||||
return CalendarEvent(
|
||||
summary=next_holiday[1],
|
||||
start=next_holiday[0],
|
||||
end=next_holiday[0],
|
||||
location=self._location,
|
||||
)
|
||||
|
||||
async def async_get_events(
|
||||
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
||||
) -> list[CalendarEvent]:
|
||||
"""Get all events in a specific time frame."""
|
||||
obj_holidays = country_holidays(
|
||||
self._country,
|
||||
subdiv=self._province,
|
||||
years=list({start_date.year, end_date.year}),
|
||||
language=self._language,
|
||||
)
|
||||
|
||||
event_list: list[CalendarEvent] = []
|
||||
|
||||
for holiday_date, holiday_name in obj_holidays.items():
|
||||
if start_date.date() <= holiday_date <= end_date.date():
|
||||
event = CalendarEvent(
|
||||
summary=holiday_name,
|
||||
start=holiday_date,
|
||||
end=holiday_date,
|
||||
location=self._location,
|
||||
)
|
||||
event_list.append(event)
|
||||
|
||||
return event_list
|
99
homeassistant/components/holiday/config_flow.py
Normal file
99
homeassistant/components/holiday/config_flow.py
Normal file
@ -0,0 +1,99 @@
|
||||
"""Config flow for Holiday integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from babel import Locale
|
||||
from holidays import list_supported_countries
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_COUNTRY
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.selector import (
|
||||
CountrySelector,
|
||||
CountrySelectorConfig,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_PROVINCE, DOMAIN
|
||||
|
||||
SUPPORTED_COUNTRIES = list_supported_countries(include_aliases=False)
|
||||
|
||||
|
||||
class HolidayConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Holiday."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
data: dict[str, Any] = {}
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
if user_input is not None:
|
||||
self.data = user_input
|
||||
|
||||
selected_country = self.data[CONF_COUNTRY]
|
||||
|
||||
if SUPPORTED_COUNTRIES[selected_country]:
|
||||
return await self.async_step_province()
|
||||
|
||||
self._async_abort_entries_match({CONF_COUNTRY: user_input[CONF_COUNTRY]})
|
||||
|
||||
locale = Locale(self.hass.config.language)
|
||||
title = locale.territories[selected_country]
|
||||
return self.async_create_entry(title=title, data=self.data)
|
||||
|
||||
user_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_COUNTRY, default=self.hass.config.country
|
||||
): CountrySelector(
|
||||
CountrySelectorConfig(
|
||||
countries=list(SUPPORTED_COUNTRIES),
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=user_schema)
|
||||
|
||||
async def async_step_province(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the province step."""
|
||||
if user_input is not None:
|
||||
combined_input: dict[str, Any] = {**self.data, **user_input}
|
||||
|
||||
country = combined_input[CONF_COUNTRY]
|
||||
province = combined_input.get(CONF_PROVINCE)
|
||||
|
||||
self._async_abort_entries_match(
|
||||
{
|
||||
CONF_COUNTRY: country,
|
||||
CONF_PROVINCE: province,
|
||||
}
|
||||
)
|
||||
|
||||
locale = Locale(self.hass.config.language)
|
||||
province_str = f", {province}" if province else ""
|
||||
name = f"{locale.territories[country]}{province_str}"
|
||||
|
||||
return self.async_create_entry(title=name, data=combined_input)
|
||||
|
||||
province_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_PROVINCE): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=SUPPORTED_COUNTRIES[self.data[CONF_COUNTRY]],
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(step_id="province", data_schema=province_schema)
|
6
homeassistant/components/holiday/const.py
Normal file
6
homeassistant/components/holiday/const.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""Constants for the Holiday integration."""
|
||||
from typing import Final
|
||||
|
||||
DOMAIN: Final = "holiday"
|
||||
|
||||
CONF_PROVINCE: Final = "province"
|
9
homeassistant/components/holiday/manifest.json
Normal file
9
homeassistant/components/holiday/manifest.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"domain": "holiday",
|
||||
"name": "Holiday",
|
||||
"codeowners": ["@jrieger"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.37", "babel==2.13.1"]
|
||||
}
|
19
homeassistant/components/holiday/strings.json
Normal file
19
homeassistant/components/holiday/strings.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Already configured. Only a single configuration for country/province combination possible."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country": "Country"
|
||||
}
|
||||
},
|
||||
"province": {
|
||||
"data": {
|
||||
"province": "Province"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -201,6 +201,7 @@ FLOWS = {
|
||||
"hisense_aehw4a1",
|
||||
"hive",
|
||||
"hlk_sw16",
|
||||
"holiday",
|
||||
"home_connect",
|
||||
"home_plus_control",
|
||||
"homeassistant_sky_connect",
|
||||
|
@ -2408,6 +2408,12 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"holiday": {
|
||||
"name": "Holiday",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"home_connect": {
|
||||
"name": "Home Connect",
|
||||
"integration_type": "hub",
|
||||
|
10
mypy.ini
10
mypy.ini
@ -1281,6 +1281,16 @@ disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.holiday.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.homeassistant.exposed_entities]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -504,6 +504,9 @@ azure-eventhub==5.11.1
|
||||
# homeassistant.components.azure_service_bus
|
||||
azure-servicebus==7.10.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
babel==2.13.1
|
||||
|
||||
# homeassistant.components.baidu
|
||||
baidu-aip==1.6.6
|
||||
|
||||
@ -1013,6 +1016,7 @@ hlk-sw16==0.0.9
|
||||
# homeassistant.components.pi_hole
|
||||
hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.37
|
||||
|
||||
|
@ -438,6 +438,9 @@ axis==48
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
|
||||
# homeassistant.components.holiday
|
||||
babel==2.13.1
|
||||
|
||||
# homeassistant.components.homekit
|
||||
base36==0.1.1
|
||||
|
||||
@ -800,6 +803,7 @@ hlk-sw16==0.0.9
|
||||
# homeassistant.components.pi_hole
|
||||
hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.37
|
||||
|
||||
|
1
tests/components/holiday/__init__.py
Normal file
1
tests/components/holiday/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the Holiday integration."""
|
14
tests/components/holiday/conftest.py
Normal file
14
tests/components/holiday/conftest.py
Normal file
@ -0,0 +1,14 @@
|
||||
"""Common fixtures for the Holiday tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.holiday.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
229
tests/components/holiday/test_calendar.py
Normal file
229
tests/components/holiday/test_calendar.py
Normal file
@ -0,0 +1,229 @@
|
||||
"""Tests for calendar platform of Holiday integration."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
|
||||
from homeassistant.components.calendar import (
|
||||
DOMAIN as CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
)
|
||||
from homeassistant.components.holiday.const import CONF_PROVINCE, DOMAIN
|
||||
from homeassistant.const import CONF_COUNTRY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_holiday_calendar_entity(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test HolidayCalendarEntity functionality."""
|
||||
freezer.move_to(datetime(2023, 1, 1, 12, tzinfo=dt_util.UTC))
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_COUNTRY: "US", CONF_PROVINCE: "AK"},
|
||||
title="United States, AK",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await async_setup_component(hass, "calendar", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
{
|
||||
"entity_id": "calendar.united_states_ak",
|
||||
"end_date_time": dt_util.now(),
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
assert response == {
|
||||
"calendar.united_states_ak": {
|
||||
"events": [
|
||||
{
|
||||
"start": "2023-01-01",
|
||||
"end": "2023-01-02",
|
||||
"summary": "New Year's Day",
|
||||
"location": "United States, AK",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
state = hass.states.get("calendar.united_states_ak")
|
||||
assert state is not None
|
||||
assert state.state == "on"
|
||||
|
||||
# Test holidays for the next year
|
||||
freezer.move_to(datetime(2023, 12, 31, 12, tzinfo=dt_util.UTC))
|
||||
|
||||
response = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
{
|
||||
"entity_id": "calendar.united_states_ak",
|
||||
"end_date_time": dt_util.now() + timedelta(days=1),
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
assert response == {
|
||||
"calendar.united_states_ak": {
|
||||
"events": [
|
||||
{
|
||||
"start": "2024-01-01",
|
||||
"end": "2024-01-02",
|
||||
"summary": "New Year's Day",
|
||||
"location": "United States, AK",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_default_language(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test default language."""
|
||||
freezer.move_to(datetime(2023, 1, 1, 12, tzinfo=dt_util.UTC))
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_COUNTRY: "FR", CONF_PROVINCE: "BL"},
|
||||
title="France, BL",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test French calendar with English language
|
||||
response = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
{
|
||||
"entity_id": "calendar.france_bl",
|
||||
"end_date_time": dt_util.now(),
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
assert response == {
|
||||
"calendar.france_bl": {
|
||||
"events": [
|
||||
{
|
||||
"start": "2023-01-01",
|
||||
"end": "2023-01-02",
|
||||
"summary": "New Year's Day",
|
||||
"location": "France, BL",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Test French calendar with French language
|
||||
hass.config.language = "fr"
|
||||
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
{
|
||||
"entity_id": "calendar.france_bl",
|
||||
"end_date_time": dt_util.now(),
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
assert response == {
|
||||
"calendar.france_bl": {
|
||||
"events": [
|
||||
{
|
||||
"start": "2023-01-01",
|
||||
"end": "2023-01-02",
|
||||
"summary": "Jour de l'an",
|
||||
"location": "France, BL",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_no_language(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test language defaults to English if language not exist."""
|
||||
freezer.move_to(datetime(2023, 1, 1, 12, tzinfo=dt_util.UTC))
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_COUNTRY: "AL"},
|
||||
title="Albania",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
response = await hass.services.async_call(
|
||||
CALENDAR_DOMAIN,
|
||||
SERVICE_GET_EVENTS,
|
||||
{
|
||||
"entity_id": "calendar.albania",
|
||||
"end_date_time": dt_util.now(),
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
assert response == {
|
||||
"calendar.albania": {
|
||||
"events": [
|
||||
{
|
||||
"start": "2023-01-01",
|
||||
"end": "2023-01-02",
|
||||
"summary": "New Year's Day",
|
||||
"location": "Albania",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_no_next_event(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test if there is no next event."""
|
||||
freezer.move_to(datetime(2023, 1, 1, 12, tzinfo=dt_util.UTC))
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_COUNTRY: "DE"},
|
||||
title="Germany",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Move time to out of reach
|
||||
freezer.move_to(datetime(dt_util.now().year + 5, 1, 1, 12, tzinfo=dt_util.UTC))
|
||||
async_fire_time_changed(hass)
|
||||
|
||||
state = hass.states.get("calendar.germany")
|
||||
assert state is not None
|
||||
assert state.state == "off"
|
||||
assert state.attributes == {"friendly_name": "Germany"}
|
128
tests/components/holiday/test_config_flow.py
Normal file
128
tests/components/holiday/test_config_flow.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""Test the Holiday config flow."""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.holiday.const import CONF_PROVINCE, DOMAIN
|
||||
from homeassistant.const import CONF_COUNTRY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_COUNTRY: "DE",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_PROVINCE: "BW",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result3["title"] == "Germany, BW"
|
||||
assert result3["data"] == {
|
||||
"country": "DE",
|
||||
"province": "BW",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_no_subdivision(hass: HomeAssistant) -> None:
|
||||
"""Test we get the forms correctly without subdivision."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_COUNTRY: "SE",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == "Sweden"
|
||||
assert result2["data"] == {
|
||||
"country": "SE",
|
||||
}
|
||||
|
||||
|
||||
async def test_form_translated_title(hass: HomeAssistant) -> None:
|
||||
"""Test the title gets translated."""
|
||||
hass.config.language = "de"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_COUNTRY: "SE",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["title"] == "Schweden"
|
||||
|
||||
|
||||
async def test_single_combination_country_province(hass: HomeAssistant) -> None:
|
||||
"""Test that configuring more than one instance is rejected."""
|
||||
data_de = {
|
||||
CONF_COUNTRY: "DE",
|
||||
CONF_PROVINCE: "BW",
|
||||
}
|
||||
data_se = {
|
||||
CONF_COUNTRY: "SE",
|
||||
}
|
||||
MockConfigEntry(domain=DOMAIN, data=data_de).add_to_hass(hass)
|
||||
MockConfigEntry(domain=DOMAIN, data=data_se).add_to_hass(hass)
|
||||
|
||||
# Test for country without subdivisions
|
||||
result_se = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=data_se,
|
||||
)
|
||||
assert result_se["type"] == FlowResultType.ABORT
|
||||
assert result_se["reason"] == "already_configured"
|
||||
|
||||
# Test for country with subdivisions
|
||||
result_de_step1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=data_de,
|
||||
)
|
||||
assert result_de_step1["type"] == FlowResultType.FORM
|
||||
|
||||
result_de_step2 = await hass.config_entries.flow.async_configure(
|
||||
result_de_step1["flow_id"],
|
||||
{
|
||||
CONF_PROVINCE: data_de[CONF_PROVINCE],
|
||||
},
|
||||
)
|
||||
assert result_de_step2["type"] == FlowResultType.ABORT
|
||||
assert result_de_step2["reason"] == "already_configured"
|
29
tests/components/holiday/test_init.py
Normal file
29
tests/components/holiday/test_init.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""Tests for the Holiday integration."""
|
||||
|
||||
from homeassistant.components.holiday.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
MOCK_CONFIG_DATA = {
|
||||
"country": "Germany",
|
||||
"province": "BW",
|
||||
}
|
||||
|
||||
|
||||
async def test_unload_entry(hass: HomeAssistant) -> None:
|
||||
"""Test removing integration."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG_DATA)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state: ConfigEntryState = entry.state
|
||||
assert state == ConfigEntryState.NOT_LOADED
|
Loading…
x
Reference in New Issue
Block a user