Files
localthings/tests/test_select_display.py
Marc Billow 6281b40549 refactor(i18n): make the shipped catalog the single source of truth
PR #68 restated its own translation data in Python: a 60-line
TRANSLATED_SELECT_STATES table of frozensets duplicating every
entity.select.*.state key, a second _TRANSLATED_COURSE_TABLES table
naming which course tables have translations, and a strings.json that
was a 835-line byte-for-byte copy of translations/en.json save 43
[%key:...%] references. Each needed hand-syncing, and one was already
drifting.

Home Assistant loads exactly one file per language for a custom
integration -- translations/<lang>.json. It never reads strings.json and
never resolves [%key:...%]; both belong to Core's build tooling, which
custom integrations don't run through (hassfest skips a missing
strings.json and validates translations/en.json instead). So en.json is
the source, and the new catalog.py reads the keys and states back out of
it for the two decisions Python genuinely has to make:

  - select._display() normalizes a raw Samsung option to a lowercase
    state key only when the catalog knows it, else leaves the vendor's
    casing alone. Derived sets are identical to the removed literals.
  - laundry.cycle_select() keys off a device-reported course table only
    when that table has an entry, else falls back to the name-only
    'cycle' key. Translating Table_00 is now a translations-only change.

Also fixes six names that had already drifted between the Python
descriptors and the catalog, restoring HA's sentence case for two
generic ones (Auto release dry, Bubble soak) and taking the catalog's
wording for the rest, and adds a test so the vestigial descriptor names
can't silently disagree with the UI again.

Claude-Session: https://claude.ai/code/session_01GiibJZZLWVvyxq7mc7EDNp
2026-07-24 19:32:09 +00:00

48 lines
1.9 KiB
Python

"""Tests for select-option display casing (custom_components/localthings/select.py)."""
from custom_components.localthings.select import _display
_UNTRANSLATED = None
_TRANSLATED = 'ice_type'
def test_display_titlecases_a_fully_lowercase_device_native_token():
"""Samsung's sound-mode field is genuinely lowercase on the wire
('voice'/'tone'/'mute') -- these have no other casing signal to key
off, so title-case them for display."""
assert _display('voice', _UNTRANSLATED) == 'Voice'
assert _display('mute', _UNTRANSLATED) == 'Mute'
def test_display_inserts_a_space_at_a_camelcase_boundary():
"""'ExtraHigh' (from supportedHeatedDry) should read as two words."""
assert _display('ExtraHigh', _UNTRANSLATED) == 'Extra High'
def test_display_passes_through_an_already_human_friendly_value():
"""'AI Wash' etc. (dishwasher cycle names) already read fine and
have no camelCase boundary or all-lowercase pattern -- must not be
mangled."""
assert _display('AI Wash', _UNTRANSLATED) == 'AI Wash'
assert _display('Low', _UNTRANSLATED) == 'Low'
assert _display('Off', _UNTRANSLATED) == 'Off'
def test_display_lowercases_for_translation_key_lookup():
"""An entity with a translation_key must match the catalog's
lowercase keys exactly -- unlike the untranslated cases above, this
is not a cosmetic transform."""
assert _display('Whiskey_IceBall_3', _TRANSLATED) == 'whiskey_iceball_3'
def test_unknown_translated_vendor_value_keeps_readable_fallback():
"""A firmware-added value must remain readable instead of being mangled."""
assert _display('FutureVendorMode', _TRANSLATED) == 'Future Vendor Mode'
def test_known_camel_case_state_uses_snake_case_translation_key():
assert _display('ExtraHigh', 'heated_dry') == 'extra_high'
def test_display_passes_through_non_string_values():
assert _display(None, _UNTRANSLATED) is None