SWS-12500-custom-component/tests/test_strings.py

84 lines
3.3 KiB
Python

"""Tests for the user-facing strings shipped with the integration.
`strings.json` is the source of truth; `translations/en.json` and `translations/cs.json`
are what the frontend actually loads. A key that exists in one file and not in another
(or a state key that no entity ever emits) is invisible in code review and only shows up
as an untranslated raw value in the UI.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import pytest
from custom_components.sws12500.sensors_weather import SENSOR_TYPES_WEATHER_API
from custom_components.sws12500.sensors_wslink import SENSOR_TYPES_WSLINK
from homeassistant.components.sensor import SensorDeviceClass
COMPONENT_DIR = Path(__file__).resolve().parents[1] / "custom_components" / "sws12500"
STRINGS_FILE = COMPONENT_DIR / "strings.json"
TRANSLATION_FILES = (
COMPONENT_DIR / "translations" / "en.json",
COMPONENT_DIR / "translations" / "cs.json",
)
def _load(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def _flatten(data: dict[str, Any], prefix: str = "") -> set[str]:
"""Return every leaf path in a translation document."""
keys: set[str] = set()
for key, value in data.items():
path = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
keys |= _flatten(value, path)
else:
keys.add(path)
return keys
@pytest.mark.parametrize("translation_file", TRANSLATION_FILES, ids=lambda p: p.name)
def test_translation_keys_match_strings_json(translation_file: Path) -> None:
"""Every shipped language must carry exactly the keys defined in strings.json."""
expected = _flatten(_load(STRINGS_FILE))
actual = _flatten(_load(translation_file))
assert not expected - actual, f"{translation_file.name} is missing keys"
assert not actual - expected, f"{translation_file.name} has stale keys"
def _enum_descriptions() -> list[Any]:
"""Sensor descriptions that report a fixed set of states to the frontend."""
seen: dict[str, Any] = {}
for description in (*SENSOR_TYPES_WEATHER_API, *SENSOR_TYPES_WSLINK):
if description.device_class is not SensorDeviceClass.ENUM or not description.options:
continue
seen.setdefault(description.translation_key or description.key, description)
return list(seen.values())
@pytest.mark.parametrize(
("path", "description"),
[(path, description) for path in (STRINGS_FILE, *TRANSLATION_FILES) for description in _enum_descriptions()],
ids=lambda value: value.name if isinstance(value, Path) else (value.translation_key or value.key),
)
def test_enum_states_are_translated(path: Path, description: Any) -> None:
"""Each enum state an entity can emit needs a translation - and no extra ones.
The battery sensors emit `UnitOfBat.UNKNOWN`, whose *value* is "drained"; a state
block keyed on the member name ("unknown") never matches, so the frontend falls back
to showing the raw state.
"""
translation_key = description.translation_key or description.key
states = _load(path)["entity"]["sensor"][translation_key]["state"]
assert set(states) == {str(option) for option in description.options}, (
f"{path.name}: state keys for '{translation_key}' do not match the emitted values"
)