diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 2c03bb9d7fc..08bd16d1f7b 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -2,7 +2,8 @@ from __future__ import annotations -from collections.abc import Callable, Generator +import asyncio +from collections.abc import AsyncGenerator, Callable, Generator from importlib.util import find_spec from pathlib import Path import string @@ -684,20 +685,54 @@ async def _check_config_flow_result_translations( ) +async def _check_create_issue_translations( + issue_registry: ir.IssueRegistry, + issue: ir.IssueEntry, + translation_errors: dict[str, str], +) -> None: + if issue.translation_key is None: + # `translation_key` is only None on dismissed issues + return + await _validate_translation( + issue_registry.hass, + translation_errors, + "issues", + issue.domain, + f"{issue.translation_key}.title", + issue.translation_placeholders, + ) + if not issue.is_fixable: + # Description is required for non-fixable issues + await _validate_translation( + issue_registry.hass, + translation_errors, + "issues", + issue.domain, + f"{issue.translation_key}.description", + issue.translation_placeholders, + ) + + @pytest.fixture(autouse=True) -def check_translations(ignore_translations: str | list[str]) -> Generator[None]: +async def check_translations( + ignore_translations: str | list[str], +) -> AsyncGenerator[None]: """Check that translation requirements are met. Current checks: - data entry flow results (ConfigFlow/OptionsFlow/RepairFlow) + - issue registry entries """ if not isinstance(ignore_translations, list): ignore_translations = [ignore_translations] translation_errors = {k: "unused" for k in ignore_translations} + translation_coros = set() + # Keep reference to original functions _original_flow_manager_async_handle_step = FlowManager._async_handle_step + _original_issue_registry_async_create_issue = ir.IssueRegistry.async_get_or_create # Prepare override functions async def _flow_manager_async_handle_step( @@ -709,13 +744,32 @@ def check_translations(ignore_translations: str | list[str]) -> Generator[None]: ) return result + def _issue_registry_async_create_issue( + self: ir.IssueRegistry, domain: str, issue_id: str, *args, **kwargs + ) -> None: + result = _original_issue_registry_async_create_issue( + self, domain, issue_id, *args, **kwargs + ) + translation_coros.add( + _check_create_issue_translations(self, result, translation_errors) + ) + return result + # Use override functions - with patch( - "homeassistant.data_entry_flow.FlowManager._async_handle_step", - _flow_manager_async_handle_step, + with ( + patch( + "homeassistant.data_entry_flow.FlowManager._async_handle_step", + _flow_manager_async_handle_step, + ), + patch( + "homeassistant.helpers.issue_registry.IssueRegistry.async_get_or_create", + _issue_registry_async_create_issue, + ), ): yield + await asyncio.gather(*translation_coros) + # Run final checks unused_ignore = [k for k, v in translation_errors.items() if v == "unused"] if unused_ignore: diff --git a/tests/components/repairs/test_init.py b/tests/components/repairs/test_init.py index edb6e509841..e78563503f1 100644 --- a/tests/components/repairs/test_init.py +++ b/tests/components/repairs/test_init.py @@ -21,6 +21,16 @@ from tests.common import mock_platform from tests.typing import WebSocketGenerator +@pytest.mark.parametrize( + "ignore_translations", + [ + [ + "component.test.issues.even_worse.title", + "component.test.issues.even_worse.description", + "component.test.issues.abc_123.title", + ] + ], +) @pytest.mark.freeze_time("2022-07-19 07:53:05") async def test_create_update_issue( hass: HomeAssistant, hass_ws_client: WebSocketGenerator @@ -160,6 +170,14 @@ async def test_create_issue_invalid_version( assert msg["result"] == {"issues": []} +@pytest.mark.parametrize( + "ignore_translations", + [ + [ + "component.test.issues.abc_123.title", + ] + ], +) @pytest.mark.freeze_time("2022-07-19 07:53:05") async def test_ignore_issue( hass: HomeAssistant, hass_ws_client: WebSocketGenerator @@ -329,6 +347,10 @@ async def test_ignore_issue( } +@pytest.mark.parametrize( + "ignore_translations", + ["component.fake_integration.issues.abc_123.title"], +) @pytest.mark.freeze_time("2022-07-19 07:53:05") async def test_delete_issue( hass: HomeAssistant, @@ -483,6 +505,10 @@ async def test_non_compliant_platform( assert list(hass.data[DOMAIN]["platforms"].keys()) == ["fake_integration"] +@pytest.mark.parametrize( + "ignore_translations", + ["component.fake_integration.issues.abc_123.title"], +) @pytest.mark.freeze_time("2022-07-21 08:22:00") async def test_sync_methods( hass: HomeAssistant, diff --git a/tests/components/repairs/test_websocket_api.py b/tests/components/repairs/test_websocket_api.py index b23977842c6..399292fb83f 100644 --- a/tests/components/repairs/test_websocket_api.py +++ b/tests/components/repairs/test_websocket_api.py @@ -151,6 +151,10 @@ async def mock_repairs_integration(hass: HomeAssistant) -> None: ) +@pytest.mark.parametrize( + "ignore_translations", + ["component.fake_integration.issues.abc_123.title"], +) async def test_dismiss_issue( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: @@ -234,6 +238,10 @@ async def test_dismiss_issue( } +@pytest.mark.parametrize( + "ignore_translations", + ["component.fake_integration.issues.abc_123.title"], +) async def test_fix_non_existing_issue( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -281,10 +289,20 @@ async def test_fix_non_existing_issue( @pytest.mark.parametrize( - ("domain", "step", "description_placeholders"), + ("domain", "step", "description_placeholders", "ignore_translations"), [ - ("fake_integration", "custom_step", None), - ("fake_integration_default_handler", "confirm", {"abc": "123"}), + ( + "fake_integration", + "custom_step", + None, + ["component.fake_integration.issues.abc_123.title"], + ), + ( + "fake_integration_default_handler", + "confirm", + {"abc": "123"}, + ["component.fake_integration_default_handler.issues.abc_123.title"], + ), ], ) async def test_fix_issue( @@ -380,6 +398,10 @@ async def test_fix_issue_unauth( assert resp.status == HTTPStatus.UNAUTHORIZED +@pytest.mark.parametrize( + "ignore_translations", + ["component.fake_integration.issues.abc_123.title"], +) async def test_get_progress_unauth( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -411,6 +433,10 @@ async def test_get_progress_unauth( assert resp.status == HTTPStatus.UNAUTHORIZED +@pytest.mark.parametrize( + "ignore_translations", + ["component.fake_integration.issues.abc_123.title"], +) async def test_step_unauth( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -442,6 +468,16 @@ async def test_step_unauth( assert resp.status == HTTPStatus.UNAUTHORIZED +@pytest.mark.parametrize( + "ignore_translations", + [ + [ + "component.test.issues.even_worse.title", + "component.test.issues.even_worse.description", + "component.test.issues.abc_123.title", + ] + ], +) @pytest.mark.freeze_time("2022-07-19 07:53:05") async def test_list_issues( hass: HomeAssistant, @@ -535,7 +571,12 @@ async def test_list_issues( @pytest.mark.parametrize( "ignore_translations", - ["component.fake_integration.issues.abc_123.fix_flow.abort.not_given"], + [ + [ + "component.fake_integration.issues.abc_123.title", + "component.fake_integration.issues.abc_123.fix_flow.abort.not_given", + ] + ], ) async def test_fix_issue_aborted( hass: HomeAssistant, @@ -598,6 +639,16 @@ async def test_fix_issue_aborted( assert msg["result"]["issues"][0] == first_issue +@pytest.mark.parametrize( + "ignore_translations", + [ + [ + "component.test.issues.abc_123.title", + "component.test.issues.even_worse.title", + "component.test.issues.even_worse.description", + ] + ], +) @pytest.mark.freeze_time("2022-07-19 07:53:05") async def test_get_issue_data( hass: HomeAssistant, hass_ws_client: WebSocketGenerator diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index 0e8c2a5e188..aec6ec84f1b 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -5432,6 +5432,17 @@ async def test_exclude_attributes(hass: HomeAssistant) -> None: assert ATTR_FRIENDLY_NAME in states[0].attributes +@pytest.mark.parametrize( + "ignore_translations", + [ + [ + "component.test.issues..title", + "component.test.issues..description", + "component.sensor.issues..title", + "component.sensor.issues..description", + ] + ], +) async def test_clean_up_repairs( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: diff --git a/tests/components/workday/test_repairs.py b/tests/components/workday/test_repairs.py index e25d4e0ca45..adbae5676e6 100644 --- a/tests/components/workday/test_repairs.py +++ b/tests/components/workday/test_repairs.py @@ -2,6 +2,8 @@ from __future__ import annotations +import pytest + from homeassistant.components.workday.const import CONF_REMOVE_HOLIDAYS, DOMAIN from homeassistant.const import CONF_COUNTRY from homeassistant.core import HomeAssistant @@ -427,6 +429,10 @@ async def test_bad_date_holiday( assert issue +@pytest.mark.parametrize( + "ignore_translations", + ["component.workday.issues.issue_1.title"], +) async def test_other_fixable_issues( hass: HomeAssistant, hass_client: ClientSessionGenerator, diff --git a/tests/components/zwave_js/test_repairs.py b/tests/components/zwave_js/test_repairs.py index 2f10b70b48a..d237a6e410a 100644 --- a/tests/components/zwave_js/test_repairs.py +++ b/tests/components/zwave_js/test_repairs.py @@ -3,6 +3,7 @@ from copy import deepcopy from unittest.mock import patch +import pytest from zwave_js_server.event import Event from zwave_js_server.model.node import Node @@ -179,6 +180,10 @@ async def test_device_config_file_changed_ignore_step( assert msg["result"]["issues"][0].get("dismissed_version") is not None +@pytest.mark.parametrize( + "ignore_translations", + ["component.zwave_js.issues.invalid_issue.title"], +) async def test_invalid_issue( hass: HomeAssistant, hass_client: ClientSessionGenerator,