Add config flow to Jewish Calendar (#84464)

* Initial commit

* add basic tests (will probably fail)

* Set basic UID for now

* Various improvements

* use new naming convention?

* bit by bit, still not working tho

* Add tz selection

* Remove failing tests

* update unique_id

* add the tests again

* revert to previous binary_sensor test

* remove translations

* apply suggestions

* remove const.py

* Address review

* revert changes

* Initial fixes for tests

* Initial commit

* add basic tests (will probably fail)

* Set basic UID for now

* Various improvements

* use new naming convention?

* bit by bit, still not working tho

* Add tz selection

* Remove failing tests

* update unique_id

* add the tests again

* revert to previous binary_sensor test

* remove translations

* apply suggestions

* remove const.py

* Address review

* revert changes

* Fix bad merges in rebase

* Get tests to run again

* Fixes due to fails in ruff/pylint

* Fix binary sensor tests

* Fix config flow tests

* Fix sensor tests

* Apply review

* Adjust candle lights

* Apply suggestion

* revert unrelated change

* Address some of the comments

* We should only allow a single jewish calendar config entry

* Make data schema easier to read

* Add test and confirm only single entry is allowed

* Move OPTIONS_SCHEMA to top of file

* Add options test

* Simplify import tests

* Test import end2end

* Use a single async_forward_entry_setups statement

* Revert schema updates for YAML schema

* Remove unneeded brackets

* Remove CONF_NAME from config_flow

* Assign hass.data[DOMAIN][config_entry.entry_id] to a local variable before creating the sensors

* Data doesn't have a name remove slugifying of it

* Test that the entry has been created correctly

* Simplify setup_entry

* Use suggested values helper and flatten location dictionary

* Remove the string for name exists as this error doesn't exist

* Remove name from config entry

* Remove _attr_has_entity_name - will be added in a subsequent PR

* Don't override entity id's - we'll fixup the naming later

* Make location optional - will by default revert to the user's home location

* Update homeassistant/components/jewish_calendar/strings.json

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* No need for local lat/long variable

* Return name attribute, will deal with it in another PR

* Revert unique_id changes, will deal with this in a subsequent PR

* Add time zone data description

* Don't break the YAML config until the user has removed it.

* Cleanup initial config flow test

---------

Co-authored-by: Tsvi Mostovicz <ttmost@gmail.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Yuval Aboulafia 2024-05-24 15:04:17 +03:00 committed by GitHub
parent 23597a8cdf
commit 2c09f72c33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 544 additions and 182 deletions

View file

@ -5,41 +5,54 @@ from __future__ import annotations
from hdate import Location
import voluptuous as vol
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_ELEVATION,
CONF_LANGUAGE,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
CONF_TIME_ZONE,
Platform,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
DOMAIN = "jewish_calendar"
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
CONF_DIASPORA = "diaspora"
CONF_LANGUAGE = "language"
CONF_CANDLE_LIGHT_MINUTES = "candle_lighting_minutes_before_sunset"
CONF_HAVDALAH_OFFSET_MINUTES = "havdalah_minutes_after_sunset"
CANDLE_LIGHT_DEFAULT = 18
DEFAULT_NAME = "Jewish Calendar"
DEFAULT_CANDLE_LIGHT = 18
DEFAULT_DIASPORA = False
DEFAULT_HAVDALAH_OFFSET_MINUTES = 0
DEFAULT_LANGUAGE = "english"
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
DOMAIN: vol.All(
cv.deprecated(DOMAIN),
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DIASPORA, default=False): cv.boolean,
vol.Optional(CONF_DIASPORA, default=DEFAULT_DIASPORA): cv.boolean,
vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude,
vol.Optional(CONF_LANGUAGE, default="english"): vol.In(
vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(
["hebrew", "english"]
),
vol.Optional(
CONF_CANDLE_LIGHT_MINUTES, default=CANDLE_LIGHT_DEFAULT
CONF_CANDLE_LIGHT_MINUTES, default=DEFAULT_CANDLE_LIGHT
): int,
# Default of 0 means use 8.5 degrees / 'three_stars' time.
vol.Optional(CONF_HAVDALAH_OFFSET_MINUTES, default=0): int,
}
vol.Optional(
CONF_HAVDALAH_OFFSET_MINUTES,
default=DEFAULT_HAVDALAH_OFFSET_MINUTES,
): int,
},
)
},
extra=vol.ALLOW_EXTRA,
@ -72,37 +85,72 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if DOMAIN not in config:
return True
name = config[DOMAIN][CONF_NAME]
language = config[DOMAIN][CONF_LANGUAGE]
async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
is_fixable=False,
breaks_in_ha_version="2024.10.0",
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": DEFAULT_NAME,
},
)
latitude = config[DOMAIN].get(CONF_LATITUDE, hass.config.latitude)
longitude = config[DOMAIN].get(CONF_LONGITUDE, hass.config.longitude)
diaspora = config[DOMAIN][CONF_DIASPORA]
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
)
)
candle_lighting_offset = config[DOMAIN][CONF_CANDLE_LIGHT_MINUTES]
havdalah_offset = config[DOMAIN][CONF_HAVDALAH_OFFSET_MINUTES]
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up a configuration entry for Jewish calendar."""
language = config_entry.data.get(CONF_LANGUAGE, DEFAULT_LANGUAGE)
diaspora = config_entry.data.get(CONF_DIASPORA, DEFAULT_DIASPORA)
candle_lighting_offset = config_entry.data.get(
CONF_CANDLE_LIGHT_MINUTES, DEFAULT_CANDLE_LIGHT
)
havdalah_offset = config_entry.data.get(
CONF_HAVDALAH_OFFSET_MINUTES, DEFAULT_HAVDALAH_OFFSET_MINUTES
)
location = Location(
latitude=latitude,
longitude=longitude,
timezone=hass.config.time_zone,
name=hass.config.location_name,
diaspora=diaspora,
latitude=config_entry.data.get(CONF_LATITUDE, hass.config.latitude),
longitude=config_entry.data.get(CONF_LONGITUDE, hass.config.longitude),
altitude=config_entry.data.get(CONF_ELEVATION, hass.config.elevation),
timezone=config_entry.data.get(CONF_TIME_ZONE, hass.config.time_zone),
)
prefix = get_unique_prefix(
location, language, candle_lighting_offset, havdalah_offset
)
hass.data[DOMAIN] = {
"location": location,
"name": name,
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = {
"language": language,
"diaspora": diaspora,
"location": location,
"candle_lighting_offset": candle_lighting_offset,
"havdalah_offset": havdalah_offset,
"diaspora": diaspora,
"prefix": prefix,
}
for platform in PLATFORMS:
hass.async_create_task(async_load_platform(hass, platform, DOMAIN, {}, config))
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok

View file

@ -6,6 +6,7 @@ from collections.abc import Callable
from dataclasses import dataclass
import datetime as dt
from datetime import datetime
from typing import Any
import hdate
from hdate.zmanim import Zmanim
@ -14,13 +15,14 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers import event
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util
from . import DOMAIN
from . import DEFAULT_NAME, DOMAIN
@dataclass(frozen=True)
@ -63,15 +65,25 @@ async def async_setup_platform(
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Jewish Calendar binary sensor devices."""
if discovery_info is None:
return
"""Set up the Jewish calendar binary sensors from YAML.
The YAML platform config is automatically
imported to a config entry, this method can be removed
when YAML support is removed.
"""
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Jewish Calendar binary sensors."""
async_add_entities(
[
JewishCalendarBinarySensor(hass.data[DOMAIN], description)
JewishCalendarBinarySensor(
hass.data[DOMAIN][config_entry.entry_id], description
)
for description in BINARY_SENSORS
]
)
@ -83,13 +95,13 @@ class JewishCalendarBinarySensor(BinarySensorEntity):
def __init__(
self,
data: dict[str, str | bool | int | float],
data: dict[str, Any],
description: JewishCalendarBinarySensorEntityDescription,
) -> None:
"""Initialize the binary sensor."""
self.entity_description = description
self._attr_name = f"{data['name']} {description.name}"
self._attr_unique_id = f"{data['prefix']}_{description.key}"
self._attr_name = f"{DEFAULT_NAME} {description.name}"
self._attr_unique_id = f'{data["prefix"]}_{description.key}'
self._location = data["location"]
self._hebrew = data["language"] == "hebrew"
self._candle_lighting_offset = data["candle_lighting_offset"]

View file

@ -0,0 +1,135 @@
"""Config flow for Jewish calendar integration."""
from __future__ import annotations
import logging
from typing import Any
import zoneinfo
import voluptuous as vol
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlowWithConfigEntry,
)
from homeassistant.const import (
CONF_ELEVATION,
CONF_LANGUAGE,
CONF_LATITUDE,
CONF_LOCATION,
CONF_LONGITUDE,
CONF_TIME_ZONE,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.selector import (
BooleanSelector,
LocationSelector,
SelectOptionDict,
SelectSelector,
SelectSelectorConfig,
)
from homeassistant.helpers.typing import ConfigType
DOMAIN = "jewish_calendar"
CONF_DIASPORA = "diaspora"
CONF_CANDLE_LIGHT_MINUTES = "candle_lighting_minutes_before_sunset"
CONF_HAVDALAH_OFFSET_MINUTES = "havdalah_minutes_after_sunset"
DEFAULT_NAME = "Jewish Calendar"
DEFAULT_CANDLE_LIGHT = 18
DEFAULT_DIASPORA = False
DEFAULT_HAVDALAH_OFFSET_MINUTES = 0
DEFAULT_LANGUAGE = "english"
LANGUAGE = [
SelectOptionDict(value="hebrew", label="Hebrew"),
SelectOptionDict(value="english", label="English"),
]
OPTIONS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_CANDLE_LIGHT_MINUTES, default=DEFAULT_CANDLE_LIGHT): int,
vol.Optional(
CONF_HAVDALAH_OFFSET_MINUTES, default=DEFAULT_HAVDALAH_OFFSET_MINUTES
): int,
}
)
_LOGGER = logging.getLogger(__name__)
def _get_data_schema(hass: HomeAssistant) -> vol.Schema:
default_location = {
CONF_LATITUDE: hass.config.latitude,
CONF_LONGITUDE: hass.config.longitude,
}
return vol.Schema(
{
vol.Required(CONF_DIASPORA, default=DEFAULT_DIASPORA): BooleanSelector(),
vol.Required(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): SelectSelector(
SelectSelectorConfig(options=LANGUAGE)
),
vol.Optional(CONF_LOCATION, default=default_location): LocationSelector(),
vol.Optional(CONF_ELEVATION, default=hass.config.elevation): int,
vol.Optional(CONF_TIME_ZONE, default=hass.config.time_zone): SelectSelector(
SelectSelectorConfig(
options=sorted(zoneinfo.available_timezones()),
)
),
}
)
class JewishCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Jewish calendar."""
VERSION = 1
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowWithConfigEntry:
"""Get the options flow for this handler."""
return JewishCalendarOptionsFlowHandler(config_entry)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
if user_input is not None:
if CONF_LOCATION in user_input:
user_input[CONF_LATITUDE] = user_input[CONF_LOCATION][CONF_LATITUDE]
user_input[CONF_LONGITUDE] = user_input[CONF_LOCATION][CONF_LONGITUDE]
return self.async_create_entry(title=DEFAULT_NAME, data=user_input)
return self.async_show_form(
step_id="user",
data_schema=self.add_suggested_values_to_schema(
_get_data_schema(self.hass), user_input
),
)
async def async_step_import(
self, import_config: ConfigType | None
) -> ConfigFlowResult:
"""Import a config entry from configuration.yaml."""
return await self.async_step_user(import_config)
class JewishCalendarOptionsFlowHandler(OptionsFlowWithConfigEntry):
"""Handle Jewish Calendar options."""
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> ConfigFlowResult:
"""Manage the Jewish Calendar options."""
if user_input is not None:
return self.async_create_entry(data=user_input)
return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
OPTIONS_SCHEMA, self.config_entry.options
),
)

View file

@ -2,8 +2,10 @@
"domain": "jewish_calendar",
"name": "Jewish Calendar",
"codeowners": ["@tsvi"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/jewish_calendar",
"iot_class": "calculated",
"loggers": ["hdate"],
"requirements": ["hdate==0.10.8"]
"requirements": ["hdate==0.10.8"],
"single_config_entry": true
}

View file

@ -1,4 +1,4 @@
"""Platform to retrieve Jewish calendar information for Home Assistant."""
"""Support for Jewish calendar sensors."""
from __future__ import annotations
@ -14,6 +14,7 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import SUN_EVENT_SUNSET
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -21,11 +22,11 @@ from homeassistant.helpers.sun import get_astral_event_date
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util
from . import DOMAIN
from . import DEFAULT_NAME, DOMAIN
_LOGGER = logging.getLogger(__name__)
INFO_SENSORS = (
INFO_SENSORS: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="date",
name="Date",
@ -53,7 +54,7 @@ INFO_SENSORS = (
),
)
TIME_SENSORS = (
TIME_SENSORS: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="first_light",
name="Alot Hashachar", # codespell:ignore alot
@ -148,17 +149,24 @@ async def async_setup_platform(
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Jewish calendar sensor platform."""
if discovery_info is None:
return
"""Set up the Jewish calendar sensors from YAML.
sensors = [
JewishCalendarSensor(hass.data[DOMAIN], description)
for description in INFO_SENSORS
]
The YAML platform config is automatically
imported to a config entry, this method can be removed
when YAML support is removed.
"""
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Jewish calendar sensors ."""
entry = hass.data[DOMAIN][config_entry.entry_id]
sensors = [JewishCalendarSensor(entry, description) for description in INFO_SENSORS]
sensors.extend(
JewishCalendarTimeSensor(hass.data[DOMAIN], description)
for description in TIME_SENSORS
JewishCalendarTimeSensor(entry, description) for description in TIME_SENSORS
)
async_add_entities(sensors)
@ -169,13 +177,13 @@ class JewishCalendarSensor(SensorEntity):
def __init__(
self,
data: dict[str, str | bool | int | float],
data: dict[str, Any],
description: SensorEntityDescription,
) -> None:
"""Initialize the Jewish calendar sensor."""
self.entity_description = description
self._attr_name = f"{data['name']} {description.name}"
self._attr_unique_id = f"{data['prefix']}_{description.key}"
self._attr_name = f"{DEFAULT_NAME} {description.name}"
self._attr_unique_id = f'{data["prefix"]}_{description.key}'
self._location = data["location"]
self._hebrew = data["language"] == "hebrew"
self._candle_lighting_offset = data["candle_lighting_offset"]

View file

@ -0,0 +1,37 @@
{
"config": {
"step": {
"user": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"diaspora": "Outside of Israel?",
"language": "Language for Holidays and Dates",
"location": "[%key:common::config_flow::data::location%]",
"elevation": "[%key:common::config_flow::data::elevation%]",
"time_zone": "Time Zone"
},
"data_description": {
"time_zone": "If you specify a location, make sure to specify the time zone for correct calendar times calculations"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"options": {
"step": {
"init": {
"title": "Configure options for Jewish Calendar",
"data": {
"candle_lighting_minutes_before_sunset": "Minutes before sunset for candle lighthing",
"havdalah_minutes_after_sunset": "Minutes after sunset for Havdalah"
},
"data_description": {
"candle_lighting_minutes_before_sunset": "Defaults to 18 minutes. In Israel you probably want to use 20/30/40 depending on your location. Outside of Israel you probably want to use 18/24.",
"havdalah_minutes_after_sunset": "Setting this to 0 means 36 minutes as fixed degrees (8.5°) will be used instead"
}
}
}
}
}

View file

@ -269,6 +269,7 @@ FLOWS = {
"isy994",
"izone",
"jellyfin",
"jewish_calendar",
"juicenet",
"justnimbus",
"jvc_projector",

View file

@ -2950,8 +2950,9 @@
"jewish_calendar": {
"name": "Jewish Calendar",
"integration_type": "hub",
"config_flow": false,
"iot_class": "calculated"
"config_flow": true,
"iot_class": "calculated",
"single_config_entry": true
},
"joaoapps_join": {
"name": "Joaoapps Join",

View file

@ -27,7 +27,7 @@ def make_nyc_test_params(dtime, results, havdalah_offset=0):
}
return (
dtime,
jewish_calendar.CANDLE_LIGHT_DEFAULT,
jewish_calendar.DEFAULT_CANDLE_LIGHT,
havdalah_offset,
True,
"America/New_York",
@ -49,7 +49,7 @@ def make_jerusalem_test_params(dtime, results, havdalah_offset=0):
}
return (
dtime,
jewish_calendar.CANDLE_LIGHT_DEFAULT,
jewish_calendar.DEFAULT_CANDLE_LIGHT,
havdalah_offset,
False,
"Asia/Jerusalem",

View file

@ -0,0 +1,28 @@
"""Common fixtures for the jewish_calendar tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.jewish_calendar import config_flow
from tests.common import MockConfigEntry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
title=config_flow.DEFAULT_NAME,
domain=config_flow.DOMAIN,
)
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.jewish_calendar.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View file

@ -1,6 +1,7 @@
"""The tests for the Jewish calendar binary sensors."""
from datetime import datetime as dt, timedelta
import logging
import pytest
@ -8,18 +9,15 @@ from homeassistant.components import jewish_calendar
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from . import (
HDATE_DEFAULT_ALTITUDE,
alter_time,
make_jerusalem_test_params,
make_nyc_test_params,
)
from . import alter_time, make_jerusalem_test_params, make_nyc_test_params
from tests.common import MockConfigEntry, async_fire_time_changed
_LOGGER = logging.getLogger(__name__)
from tests.common import async_fire_time_changed
MELACHA_PARAMS = [
make_nyc_test_params(
@ -170,7 +168,6 @@ MELACHA_TEST_IDS = [
)
async def test_issur_melacha_sensor(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
now,
candle_lighting,
havdalah,
@ -189,49 +186,33 @@ async def test_issur_melacha_sensor(
hass.config.longitude = longitude
with alter_time(test_time):
assert await async_setup_component(
hass,
jewish_calendar.DOMAIN,
{
"jewish_calendar": {
"name": "test",
entry = MockConfigEntry(
domain=jewish_calendar.DOMAIN,
data={
"language": "english",
"diaspora": diaspora,
"candle_lighting_minutes_before_sunset": candle_lighting,
"havdalah_minutes_after_sunset": havdalah,
}
},
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert (
hass.states.get("binary_sensor.test_issur_melacha_in_effect").state
hass.states.get(
"binary_sensor.jewish_calendar_issur_melacha_in_effect"
).state
== result["state"]
)
entity = entity_registry.async_get("binary_sensor.test_issur_melacha_in_effect")
target_uid = "_".join(
map(
str,
[
latitude,
longitude,
tzname,
HDATE_DEFAULT_ALTITUDE,
diaspora,
"english",
candle_lighting,
havdalah,
"issur_melacha_in_effect",
],
)
)
assert entity.unique_id == target_uid
with alter_time(result["update"]):
async_fire_time_changed(hass, result["update"])
await hass.async_block_till_done()
assert (
hass.states.get("binary_sensor.test_issur_melacha_in_effect").state
hass.states.get(
"binary_sensor.jewish_calendar_issur_melacha_in_effect"
).state
== result["new_state"]
)
@ -277,22 +258,22 @@ async def test_issur_melacha_sensor_update(
hass.config.longitude = longitude
with alter_time(test_time):
assert await async_setup_component(
hass,
jewish_calendar.DOMAIN,
{
"jewish_calendar": {
"name": "test",
entry = MockConfigEntry(
domain=jewish_calendar.DOMAIN,
data={
"language": "english",
"diaspora": diaspora,
"candle_lighting_minutes_before_sunset": candle_lighting,
"havdalah_minutes_after_sunset": havdalah,
}
},
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert (
hass.states.get("binary_sensor.test_issur_melacha_in_effect").state
hass.states.get(
"binary_sensor.jewish_calendar_issur_melacha_in_effect"
).state
== result[0]
)
@ -301,7 +282,9 @@ async def test_issur_melacha_sensor_update(
async_fire_time_changed(hass, test_time)
await hass.async_block_till_done()
assert (
hass.states.get("binary_sensor.test_issur_melacha_in_effect").state
hass.states.get(
"binary_sensor.jewish_calendar_issur_melacha_in_effect"
).state
== result[1]
)

View file

@ -0,0 +1,138 @@
"""Test the Jewish calendar config flow."""
from unittest.mock import AsyncMock
import pytest
from homeassistant import config_entries, setup
from homeassistant.components.jewish_calendar import (
CONF_CANDLE_LIGHT_MINUTES,
CONF_DIASPORA,
CONF_HAVDALAH_OFFSET_MINUTES,
CONF_LANGUAGE,
DEFAULT_CANDLE_LIGHT,
DEFAULT_DIASPORA,
DEFAULT_HAVDALAH_OFFSET_MINUTES,
DEFAULT_LANGUAGE,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import (
CONF_ELEVATION,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
CONF_TIME_ZONE,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
async def test_step_user(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test user config."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_DIASPORA: DEFAULT_DIASPORA, CONF_LANGUAGE: DEFAULT_LANGUAGE},
)
assert result2["type"] is FlowResultType.CREATE_ENTRY
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 1
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data[CONF_DIASPORA] == DEFAULT_DIASPORA
assert entries[0].data[CONF_LANGUAGE] == DEFAULT_LANGUAGE
assert entries[0].data[CONF_LATITUDE] == hass.config.latitude
assert entries[0].data[CONF_LONGITUDE] == hass.config.longitude
assert entries[0].data[CONF_ELEVATION] == hass.config.elevation
assert entries[0].data[CONF_TIME_ZONE] == hass.config.time_zone
@pytest.mark.parametrize("diaspora", [True, False])
@pytest.mark.parametrize("language", ["hebrew", "english"])
async def test_import_no_options(hass: HomeAssistant, language, diaspora) -> None:
"""Test that the import step works."""
conf = {
DOMAIN: {CONF_NAME: "test", CONF_LANGUAGE: language, CONF_DIASPORA: diaspora}
}
assert await async_setup_component(hass, DOMAIN, conf.copy())
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data == conf[DOMAIN] | {
CONF_CANDLE_LIGHT_MINUTES: DEFAULT_CANDLE_LIGHT,
CONF_HAVDALAH_OFFSET_MINUTES: DEFAULT_HAVDALAH_OFFSET_MINUTES,
}
async def test_import_with_options(hass: HomeAssistant) -> None:
"""Test that the import step works."""
conf = {
DOMAIN: {
CONF_NAME: "test",
CONF_DIASPORA: DEFAULT_DIASPORA,
CONF_LANGUAGE: DEFAULT_LANGUAGE,
CONF_CANDLE_LIGHT_MINUTES: 20,
CONF_HAVDALAH_OFFSET_MINUTES: 50,
CONF_LATITUDE: 31.76,
CONF_LONGITUDE: 35.235,
}
}
assert await async_setup_component(hass, DOMAIN, conf.copy())
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data == conf[DOMAIN]
async def test_single_instance_allowed(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test we abort if already setup."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result.get("type") is FlowResultType.ABORT
assert result.get("reason") == "single_instance_allowed"
async def test_options(hass: HomeAssistant, mock_config_entry: MockConfigEntry) -> None:
"""Test updating options."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_CANDLE_LIGHT_MINUTES: 25,
CONF_HAVDALAH_OFFSET_MINUTES: 34,
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"][CONF_CANDLE_LIGHT_MINUTES] == 25
assert result["data"][CONF_HAVDALAH_OFFSET_MINUTES] == 34

View file

@ -7,34 +7,28 @@ import pytest
from homeassistant.components import jewish_calendar
from homeassistant.components.binary_sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from . import (
HDATE_DEFAULT_ALTITUDE,
alter_time,
make_jerusalem_test_params,
make_nyc_test_params,
)
from . import alter_time, make_jerusalem_test_params, make_nyc_test_params
from tests.common import async_fire_time_changed
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_jewish_calendar_min_config(hass: HomeAssistant) -> None:
"""Test minimum jewish calendar configuration."""
assert await async_setup_component(
hass, jewish_calendar.DOMAIN, {"jewish_calendar": {}}
)
entry = MockConfigEntry(domain=jewish_calendar.DOMAIN, data={})
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("sensor.jewish_calendar_date") is not None
async def test_jewish_calendar_hebrew(hass: HomeAssistant) -> None:
"""Test jewish calendar sensor with language set to hebrew."""
assert await async_setup_component(
hass, jewish_calendar.DOMAIN, {"jewish_calendar": {"language": "hebrew"}}
)
entry = MockConfigEntry(domain=jewish_calendar.DOMAIN, data={"language": "hebrew"})
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("sensor.jewish_calendar_date") is not None
@ -172,17 +166,15 @@ async def test_jewish_calendar_sensor(
hass.config.longitude = longitude
with alter_time(test_time):
assert await async_setup_component(
hass,
jewish_calendar.DOMAIN,
{
"jewish_calendar": {
"name": "test",
entry = MockConfigEntry(
domain=jewish_calendar.DOMAIN,
data={
"language": language,
"diaspora": diaspora,
}
},
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
future = dt_util.utcnow() + timedelta(seconds=30)
@ -195,7 +187,7 @@ async def test_jewish_calendar_sensor(
else result
)
sensor_object = hass.states.get(f"sensor.test_{sensor}")
sensor_object = hass.states.get(f"sensor.jewish_calendar_{sensor}")
assert sensor_object.state == result
if sensor == "holiday":
@ -497,7 +489,6 @@ SHABBAT_TEST_IDS = [
)
async def test_shabbat_times_sensor(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
language,
now,
candle_lighting,
@ -517,19 +508,17 @@ async def test_shabbat_times_sensor(
hass.config.longitude = longitude
with alter_time(test_time):
assert await async_setup_component(
hass,
jewish_calendar.DOMAIN,
{
"jewish_calendar": {
"name": "test",
entry = MockConfigEntry(
domain=jewish_calendar.DOMAIN,
data={
"language": language,
"diaspora": diaspora,
"candle_lighting_minutes_before_sunset": candle_lighting,
"havdalah_minutes_after_sunset": havdalah,
}
},
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
future = dt_util.utcnow() + timedelta(seconds=30)
@ -548,30 +537,10 @@ async def test_shabbat_times_sensor(
else result_value
)
assert hass.states.get(f"sensor.test_{sensor_type}").state == str(
assert hass.states.get(f"sensor.jewish_calendar_{sensor_type}").state == str(
result_value
), f"Value for {sensor_type}"
entity = entity_registry.async_get(f"sensor.test_{sensor_type}")
target_sensor_type = sensor_type.replace("parshat_hashavua", "weekly_portion")
target_uid = "_".join(
map(
str,
[
latitude,
longitude,
tzname,
HDATE_DEFAULT_ALTITUDE,
diaspora,
language,
candle_lighting,
havdalah,
target_sensor_type,
],
)
)
assert entity.unique_id == target_uid
OMER_PARAMS = [
(dt(2019, 4, 21, 0), "1"),
@ -597,16 +566,16 @@ async def test_omer_sensor(hass: HomeAssistant, test_time, result) -> None:
test_time = test_time.replace(tzinfo=dt_util.get_time_zone(hass.config.time_zone))
with alter_time(test_time):
assert await async_setup_component(
hass, jewish_calendar.DOMAIN, {"jewish_calendar": {"name": "test"}}
)
entry = MockConfigEntry(domain=jewish_calendar.DOMAIN)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
future = dt_util.utcnow() + timedelta(seconds=30)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_day_of_the_omer").state == result
assert hass.states.get("sensor.jewish_calendar_day_of_the_omer").state == result
DAFYOMI_PARAMS = [
@ -631,16 +600,16 @@ async def test_dafyomi_sensor(hass: HomeAssistant, test_time, result) -> None:
test_time = test_time.replace(tzinfo=dt_util.get_time_zone(hass.config.time_zone))
with alter_time(test_time):
assert await async_setup_component(
hass, jewish_calendar.DOMAIN, {"jewish_calendar": {"name": "test"}}
)
entry = MockConfigEntry(domain=jewish_calendar.DOMAIN)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
future = dt_util.utcnow() + timedelta(seconds=30)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_daf_yomi").state == result
assert hass.states.get("sensor.jewish_calendar_daf_yomi").state == result
async def test_no_discovery_info(