diff --git a/homeassistant/components/holiday/config_flow.py b/homeassistant/components/holiday/config_flow.py index 00a71351ca7781..62177915979fd3 100644 --- a/homeassistant/components/holiday/config_flow.py +++ b/homeassistant/components/holiday/config_flow.py @@ -19,6 +19,7 @@ from homeassistant.helpers.selector import ( CountrySelector, CountrySelectorConfig, + SelectOptionDict, SelectSelector, SelectSelectorConfig, SelectSelectorMode, @@ -30,6 +31,32 @@ SUPPORTED_COUNTRIES = list_supported_countries(include_aliases=False) +def get_optional_provinces(country: str) -> list[Any]: + """Return the country provinces (territories). + + Some territories can have extra or different holidays + from another within the same country. + Some territories can have different names (aliases). + """ + province_options: list[Any] = [] + + if provinces := SUPPORTED_COUNTRIES[country]: + country_data = country_holidays(country, years=dt_util.utcnow().year) + if country_data.subdivisions_aliases and ( + subdiv_aliases := country_data.get_subdivision_aliases() + ): + # Remember the user choice among all of the aliases available + province_options = [ + SelectOptionDict(value=alias, label=alias) + for aliases in subdiv_aliases.values() + for alias in aliases + ] + else: + province_options = provinces + + return province_options + + def get_optional_categories(country: str) -> list[str]: """Return the country categories. @@ -45,7 +72,7 @@ def get_optional_categories(country: str) -> list[str]: def get_options_schema(country: str) -> vol.Schema: """Return the options schema.""" schema = {} - if provinces := SUPPORTED_COUNTRIES[country]: + if provinces := get_optional_provinces(country): schema[vol.Optional(CONF_PROVINCE)] = SelectSelector( SelectSelectorConfig( options=provinces, @@ -64,6 +91,12 @@ def get_options_schema(country: str) -> vol.Schema: return vol.Schema(schema) +def get_province_code(country: str, province: str) -> str: + """Return the ISO 3166 code for a given province alias, if any.""" + country_data = country_holidays(country, province, years=dt_util.utcnow().year) + return country_data.subdivisions_aliases.get(province, province) + + class HolidayConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Holiday.""" @@ -128,7 +161,11 @@ async def async_step_options( data = {CONF_COUNTRY: country} options: dict[str, Any] | None = None if province := user_input.get(CONF_PROVINCE): - data[CONF_PROVINCE] = province + # Don't store the user alias but the stable ISO 3166 code + province_code = await self.hass.async_add_executor_job( + get_province_code, country, province + ) + data[CONF_PROVINCE] = province_code if categories := user_input.get(CONF_CATEGORIES): options = {CONF_CATEGORIES: categories} @@ -165,7 +202,11 @@ async def async_step_reconfigure( data = {CONF_COUNTRY: country} options: dict[str, Any] | None = None if province := user_input.get(CONF_PROVINCE): - data[CONF_PROVINCE] = province + # Don't store the user alias but the stable ISO 3166 code + province_code = await self.hass.async_add_executor_job( + get_province_code, country, province + ) + data[CONF_PROVINCE] = province_code if categories := user_input.get(CONF_CATEGORIES): options = {CONF_CATEGORIES: categories} diff --git a/tests/components/holiday/test_config_flow.py b/tests/components/holiday/test_config_flow.py index f561c4a4b9fd18..979783dfc1c65f 100644 --- a/tests/components/holiday/test_config_flow.py +++ b/tests/components/holiday/test_config_flow.py @@ -41,13 +41,13 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { - CONF_PROVINCE: "BW", + CONF_PROVINCE: "Baden-Württemberg", }, ) await hass.async_block_till_done() assert result3["type"] is FlowResultType.CREATE_ENTRY - assert result3["title"] == "Germany, BW" + assert result3["title"] == "Germany, Baden-Württemberg" assert result3["data"] == { "country": "DE", "province": "BW", @@ -131,7 +131,8 @@ async def test_single_combination_country_province(hass: HomeAssistant) -> None: result_de_step2 = await hass.config_entries.flow.async_configure( result_de_step1["flow_id"], { - CONF_PROVINCE: data_de[CONF_PROVINCE], + # The name must be used, not the stored ISO code + CONF_PROVINCE: "Baden-Württemberg", }, ) assert result_de_step2["type"] is FlowResultType.ABORT @@ -172,13 +173,13 @@ async def test_form_babel_unresolved_language(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_PROVINCE: "BW", + CONF_PROVINCE: "Baden-Württemberg", }, ) await hass.async_block_till_done() assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "Germany, BW" + assert result["title"] == "Germany, Baden-Württemberg" assert result["data"] == { "country": "DE", "province": "BW", @@ -219,13 +220,13 @@ async def test_form_babel_replace_dash_with_underscore(hass: HomeAssistant) -> N result = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_PROVINCE: "BW", + CONF_PROVINCE: "Baden-Württemberg", }, ) await hass.async_block_till_done() assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "Germany, BW" + assert result["title"] == "Germany, Baden-Württemberg" assert result["data"] == { "country": "DE", "province": "BW", @@ -237,7 +238,7 @@ async def test_reconfigure(hass: HomeAssistant) -> None: """Test reconfigure flow.""" entry = MockConfigEntry( domain=DOMAIN, - title="Germany, BW", + title="Germany, Baden-Württemberg", data={"country": "DE", "province": "BW"}, ) entry.add_to_hass(hass) @@ -248,7 +249,7 @@ async def test_reconfigure(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_PROVINCE: "NW", + CONF_PROVINCE: "Nordrhein-Westfalen", }, ) await hass.async_block_till_done() @@ -256,7 +257,7 @@ async def test_reconfigure(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.ABORT assert result["reason"] == "reconfigure_successful" entry = hass.config_entries.async_get_entry(entry.entry_id) - assert entry.title == "Germany, NW" + assert entry.title == "Germany, Nordrhein-Westfalen" assert entry.data == {"country": "DE", "province": "NW"} @@ -297,7 +298,7 @@ async def test_reconfigure_incorrect_language(hass: HomeAssistant) -> None: entry = MockConfigEntry( domain=DOMAIN, - title="Germany, BW", + title="Germany, Baden-Württemberg", data={"country": "DE", "province": "BW"}, ) entry.add_to_hass(hass) @@ -308,7 +309,7 @@ async def test_reconfigure_incorrect_language(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_PROVINCE: "NW", + CONF_PROVINCE: "Nordrhein-Westfalen", }, ) await hass.async_block_till_done() @@ -316,7 +317,7 @@ async def test_reconfigure_incorrect_language(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.ABORT assert result["reason"] == "reconfigure_successful" entry = hass.config_entries.async_get_entry(entry.entry_id) - assert entry.title == "Germany, NW" + assert entry.title == "Germany, Nordrhein-Westfalen" assert entry.data == {"country": "DE", "province": "NW"} @@ -325,13 +326,13 @@ async def test_reconfigure_entry_exists(hass: HomeAssistant) -> None: """Test reconfigure flow stops if other entry already exist.""" entry = MockConfigEntry( domain=DOMAIN, - title="Germany, BW", + title="Germany, Baden-Württemberg", data={"country": "DE", "province": "BW"}, ) entry.add_to_hass(hass) entry2 = MockConfigEntry( domain=DOMAIN, - title="Germany, NW", + title="Germany, Nordrhein-Westfalen", data={"country": "DE", "province": "NW"}, ) entry2.add_to_hass(hass) @@ -342,7 +343,7 @@ async def test_reconfigure_entry_exists(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_configure( result["flow_id"], { - CONF_PROVINCE: "NW", + CONF_PROVINCE: "Nordrhein-Westfalen", }, ) await hass.async_block_till_done() @@ -350,7 +351,7 @@ async def test_reconfigure_entry_exists(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.ABORT assert result["reason"] == "already_configured" entry = hass.config_entries.async_get_entry(entry.entry_id) - assert entry.title == "Germany, BW" + assert entry.title == "Germany, Baden-Württemberg" assert entry.data == {"country": "DE", "province": "BW"} @@ -425,6 +426,41 @@ async def test_form_with_options( assert state.state == STATE_OFF +async def test_form_with_subdivision_aliases(hass: HomeAssistant) -> None: + """Test the flow with several aliases using the same subdivision code.""" + await hass.config.async_set_time_zone("Europe/Paris") + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] is FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_COUNTRY: "FR", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_PROVINCE: "Alsace", + }, + ) + await hass.async_block_till_done(wait_background_tasks=True) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "France, Alsace" + assert result["data"] == { + CONF_COUNTRY: "FR", + CONF_PROVINCE: "GES", + } + + @pytest.mark.usefixtures("mock_setup_entry") async def test_options_abort_no_categories(hass: HomeAssistant) -> None: """Test the options flow abort if no categories to select."""