From 49715f300a536bfd55dcdb14418ec8723e0e606c Mon Sep 17 00:00:00 2001 From: Scott Colby Date: Sun, 24 Sep 2023 10:13:45 -0400 Subject: [PATCH] Allow workday sensor to be configured without a country (#93048) * Merge branch 'dev' into workday_without_country * ruff * remove province check * Remove not needed test * Mod config flow --------- Co-authored-by: G Johansson --- homeassistant/components/workday/__init__.py | 5 ++- .../components/workday/binary_sensor.py | 20 +++++---- .../components/workday/config_flow.py | 28 ++++++++----- homeassistant/components/workday/strings.json | 5 +++ tests/components/workday/__init__.py | 16 +++++++ .../components/workday/test_binary_sensor.py | 32 +++++++++++++- tests/components/workday/test_config_flow.py | 42 +++++++++++++++++++ 7 files changed, 126 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/workday/__init__.py b/homeassistant/components/workday/__init__.py index 84ed67a36dd..c3bf7f2efd5 100644 --- a/homeassistant/components/workday/__init__.py +++ b/homeassistant/components/workday/__init__.py @@ -13,12 +13,13 @@ from .const import CONF_COUNTRY, CONF_PROVINCE, PLATFORMS async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Workday from a config entry.""" - country: str = entry.options[CONF_COUNTRY] + country: str | None = entry.options.get(CONF_COUNTRY) province: str | None = entry.options.get(CONF_PROVINCE) + if country and country not in list_supported_countries(): raise ConfigEntryError(f"Selected country {country} is not valid") - if province and province not in list_supported_countries()[country]: + if country and province and province not in list_supported_countries()[country]: raise ConfigEntryError( f"Selected province {province} for country {country} is not valid" ) diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index ad18c8863d6..b60346c3bbb 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -121,21 +121,25 @@ async def async_setup_entry( """Set up the Workday sensor.""" add_holidays: list[DateLike] = entry.options[CONF_ADD_HOLIDAYS] remove_holidays: list[str] = entry.options[CONF_REMOVE_HOLIDAYS] - country: str = entry.options[CONF_COUNTRY] + country: str | None = entry.options.get(CONF_COUNTRY) days_offset: int = int(entry.options[CONF_OFFSET]) excludes: list[str] = entry.options[CONF_EXCLUDES] province: str | None = entry.options.get(CONF_PROVINCE) sensor_name: str = entry.options[CONF_NAME] workdays: list[str] = entry.options[CONF_WORKDAYS] + year: int = (dt_util.now() + timedelta(days=days_offset)).year - cls: HolidayBase = country_holidays(country, subdiv=province, years=year) - obj_holidays: HolidayBase = country_holidays( - country, - subdiv=province, - years=year, - language=cls.default_language, - ) + if country: + cls: HolidayBase = country_holidays(country, subdiv=province, years=year) + obj_holidays: HolidayBase = country_holidays( + country, + subdiv=province, + years=year, + language=cls.default_language, + ) + else: + obj_holidays = HolidayBase() # Add custom holidays try: diff --git a/homeassistant/components/workday/config_flow.py b/homeassistant/components/workday/config_flow.py index 54c6196b75b..df74fff83e1 100644 --- a/homeassistant/components/workday/config_flow.py +++ b/homeassistant/components/workday/config_flow.py @@ -52,7 +52,7 @@ def add_province_to_schema( ) -> vol.Schema: """Update schema with province from country.""" all_countries = list_supported_countries() - if not all_countries[country]: + if not all_countries.get(country): return schema province_list = [NONE_SENTINEL, *all_countries[country]] @@ -71,19 +71,21 @@ def add_province_to_schema( def validate_custom_dates(user_input: dict[str, Any]) -> None: """Validate custom dates for add/remove holidays.""" - for add_date in user_input[CONF_ADD_HOLIDAYS]: if dt_util.parse_date(add_date) is None: raise AddDatesError("Incorrect date") - cls: HolidayBase = country_holidays(user_input[CONF_COUNTRY]) year: int = dt_util.now().year - obj_holidays: HolidayBase = country_holidays( - user_input[CONF_COUNTRY], - subdiv=user_input.get(CONF_PROVINCE), - years=year, - language=cls.default_language, - ) + if country := user_input[CONF_COUNTRY]: + cls = country_holidays(country) + obj_holidays = country_holidays( + country=country, + subdiv=user_input.get(CONF_PROVINCE), + years=year, + language=cls.default_language, + ) + else: + obj_holidays = HolidayBase(years=year) for remove_date in user_input[CONF_REMOVE_HOLIDAYS]: if dt_util.parse_date(remove_date) is None: @@ -94,10 +96,11 @@ def validate_custom_dates(user_input: dict[str, Any]) -> None: DATA_SCHEMA_SETUP = vol.Schema( { vol.Required(CONF_NAME, default=DEFAULT_NAME): TextSelector(), - vol.Required(CONF_COUNTRY): SelectSelector( + vol.Optional(CONF_COUNTRY, default=NONE_SENTINEL): SelectSelector( SelectSelectorConfig( - options=list(list_supported_countries()), + options=[NONE_SENTINEL, *list(list_supported_countries())], mode=SelectSelectorMode.DROPDOWN, + translation_key=CONF_COUNTRY, ) ), } @@ -208,6 +211,9 @@ class WorkdayConfigFlow(ConfigFlow, domain=DOMAIN): errors: dict[str, str] = {} if user_input is not None: combined_input: dict[str, Any] = {**self.data, **user_input} + + if combined_input.get(CONF_COUNTRY, NONE_SENTINEL) == NONE_SENTINEL: + combined_input[CONF_COUNTRY] = None if combined_input.get(CONF_PROVINCE, NONE_SENTINEL) == NONE_SENTINEL: combined_input[CONF_PROVINCE] = None diff --git a/homeassistant/components/workday/strings.json b/homeassistant/components/workday/strings.json index a217a7a36b1..b4bad4796bc 100644 --- a/homeassistant/components/workday/strings.json +++ b/homeassistant/components/workday/strings.json @@ -66,6 +66,11 @@ } }, "selector": { + "country": { + "options": { + "none": "No country" + } + }, "province": { "options": { "none": "No subdivision" diff --git a/tests/components/workday/__init__.py b/tests/components/workday/__init__.py index f87328998e1..2a1b61a0a0f 100644 --- a/tests/components/workday/__init__.py +++ b/tests/components/workday/__init__.py @@ -40,6 +40,22 @@ async def init_integration( return config_entry +TEST_CONFIG_NO_COUNTRY = { + "name": DEFAULT_NAME, + "excludes": DEFAULT_EXCLUDES, + "days_offset": DEFAULT_OFFSET, + "workdays": DEFAULT_WORKDAYS, + "add_holidays": [], + "remove_holidays": [], +} +TEST_CONFIG_NO_COUNTRY_ADD_HOLIDAY = { + "name": DEFAULT_NAME, + "excludes": DEFAULT_EXCLUDES, + "days_offset": DEFAULT_OFFSET, + "workdays": DEFAULT_WORKDAYS, + "add_holidays": ["2020-02-24"], + "remove_holidays": [], +} TEST_CONFIG_WITH_PROVINCE = { "name": DEFAULT_NAME, "country": "DE", diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index 51280c8d75c..a3923bfb291 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -19,6 +19,8 @@ from . import ( TEST_CONFIG_INCORRECT_ADD_REMOVE, TEST_CONFIG_INCORRECT_COUNTRY, TEST_CONFIG_INCORRECT_PROVINCE, + TEST_CONFIG_NO_COUNTRY, + TEST_CONFIG_NO_COUNTRY_ADD_HOLIDAY, TEST_CONFIG_NO_PROVINCE, TEST_CONFIG_NO_STATE, TEST_CONFIG_REMOVE_HOLIDAY, @@ -49,6 +51,7 @@ async def test_valid_country_yaml() -> None: @pytest.mark.parametrize( ("config", "expected_state"), [ + (TEST_CONFIG_NO_COUNTRY, "on"), (TEST_CONFIG_WITH_PROVINCE, "off"), (TEST_CONFIG_NO_PROVINCE, "off"), (TEST_CONFIG_WITH_STATE, "on"), @@ -71,6 +74,7 @@ async def test_setup( await init_integration(hass, config) state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None assert state.state == expected_state assert state.attributes == { "friendly_name": "Workday Sensor", @@ -99,6 +103,7 @@ async def test_setup_from_import( await hass.async_block_till_done() state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None assert state.state == "off" assert state.attributes == { "friendly_name": "Workday Sensor", @@ -110,7 +115,6 @@ async def test_setup_from_import( async def test_setup_with_invalid_province_from_yaml(hass: HomeAssistant) -> None: """Test setup invalid province with import.""" - await async_setup_component( hass, "binary_sensor", @@ -137,11 +141,20 @@ async def test_setup_with_working_holiday( await init_integration(hass, TEST_CONFIG_INCLUDE_HOLIDAY) state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None assert state.state == "on" +@pytest.mark.parametrize( + "config", + [ + TEST_CONFIG_EXAMPLE_2, + TEST_CONFIG_NO_COUNTRY_ADD_HOLIDAY, + ], +) async def test_setup_add_holiday( hass: HomeAssistant, + config: dict[str, Any], freezer: FrozenDateTimeFactory, ) -> None: """Test setup from various configs.""" @@ -149,6 +162,20 @@ async def test_setup_add_holiday( await init_integration(hass, TEST_CONFIG_EXAMPLE_2) state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None + assert state.state == "off" + + +async def test_setup_no_country_weekend( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, +) -> None: + """Test setup shows weekend as non-workday with no country.""" + freezer.move_to(datetime(2020, 2, 23, 12, tzinfo=UTC)) # Sunday + await init_integration(hass, TEST_CONFIG_NO_COUNTRY) + + state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None assert state.state == "off" @@ -161,6 +188,7 @@ async def test_setup_remove_holiday( await init_integration(hass, TEST_CONFIG_REMOVE_HOLIDAY) state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None assert state.state == "on" @@ -173,6 +201,7 @@ async def test_setup_remove_holiday_named( await init_integration(hass, TEST_CONFIG_REMOVE_NAMED) state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None assert state.state == "on" @@ -185,6 +214,7 @@ async def test_setup_day_after_tomorrow( await init_integration(hass, TEST_CONFIG_DAY_AFTER_TOMORROW) state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None assert state.state == "off" diff --git a/tests/components/workday/test_config_flow.py b/tests/components/workday/test_config_flow.py index 7e28471c78c..78cbbf97fed 100644 --- a/tests/components/workday/test_config_flow.py +++ b/tests/components/workday/test_config_flow.py @@ -72,6 +72,48 @@ async def test_form(hass: HomeAssistant) -> None: } +async def test_form_no_country(hass: HomeAssistant) -> None: + """Test we get the forms correctly without a country.""" + + 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_NAME: "Workday Sensor", + CONF_COUNTRY: "none", + }, + ) + await hass.async_block_till_done() + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + CONF_EXCLUDES: DEFAULT_EXCLUDES, + CONF_OFFSET: DEFAULT_OFFSET, + CONF_WORKDAYS: DEFAULT_WORKDAYS, + CONF_ADD_HOLIDAYS: [], + CONF_REMOVE_HOLIDAYS: [], + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == FlowResultType.CREATE_ENTRY + assert result3["title"] == "Workday Sensor" + assert result3["options"] == { + "name": "Workday Sensor", + "country": None, + "excludes": ["sat", "sun", "holiday"], + "days_offset": 0, + "workdays": ["mon", "tue", "wed", "thu", "fri"], + "add_holidays": [], + "remove_holidays": [], + "province": None, + } + + async def test_form_no_subdivision(hass: HomeAssistant) -> None: """Test we get the forms correctly without subdivision."""