Files
localthings/tests/test_dishwasher_capabilities.py
T
Lukas Knoeller 75466761a1 Normalize appliance enums for HA translations
Translates status values the integration previously surfaced as raw
Samsung strings: cycle progress ('Rinse' -> "Rinsing"), diagnosis state,
buzzer volume options, and the drum-clean counter's unit. Progress and
diagnosis become enum sensors so Home Assistant looks their state up in
the catalog; progress keys come from lowercasing the device's own value
rather than a hardcoded map, so adding a language is a catalog-only
change (PR #341 review).

Rebased onto main, which has since gained the issue #345 sticky hold, and
fixed up for two problems that combination exposes:

Home Assistant refuses an enum state that isn't in the sensor's options,
which takes the entity out rather than degrading it. The sticky hold froze
progress at the device's raw 'Finish' while rep_fn had been normalized to
'finish', so every completed cycle -- the exact path #345 exists to serve
-- would have produced a rejected state.

Separately, options built from the catalog can only ever list values we
have a translation for, while this registry's rule is that an unrecognized
device value renders raw. Every progress token the shipped fixtures
advertise is covered today, but Samsung ships more devices than we have
dumps for, so the sensor platform now admits the live value into its own
options: known values translate, unknown ones display untranslated instead
of breaking the entity.

The drum-clean unit moves from a native unit to the catalog because Home
Assistant rejects an entity declaring both. Note it resolves against the
default language, so the localized unit strings are inert -- kept only
because every catalog must mirror English key for key.

Also moves the diagnosis normalizer to common.py, so dryer.py doesn't
import a private symbol from dishwasher.py to get it.
2026-08-15 01:40:56 +00:00

74 lines
3.0 KiB
Python

"""Tests for dishwasher-specific capabilities.
The shared /course/vs/0 cycle-select machinery (parse_edit_course_list,
cycle_options, cycle_write) is tested in test_laundry_capabilities.py; here we
check the dishwasher wiring and its device-specific options.
"""
from custom_components.localthings.registry.capabilities import dishwasher
from custom_components.localthings.registry.entities import SensorDesc, SwitchDesc
class TestCycleOptions:
def _cycle(self):
return next(e for e in dishwasher.CYCLE_OPTIONS.entities if e.key == "cycle")
def test_cycle_desc_uses_shared_cycle_options(self):
desc = self._cycle()
live = {"/wm/editcourse/vs/0": {"x.com.samsung.da.editCourseList": "EditCourseList_0E82"}}
assert desc.options(live) == ["0E", "82"]
assert desc.translation_key == "dishwasher_cycle"
def test_exists_only_when_edit_course_list_is_live(self):
desc = self._cycle()
assert desc.exists_fn({}, {}) is False
assert desc.exists_fn({}, {"/wm/editcourse/vs/0": {}}) is False
live = {"/wm/editcourse/vs/0": {"x.com.samsung.da.editCourseList": "EditCourseList_0E"}}
assert desc.exists_fn({}, live) is True
def test_cycle_write_uses_raw_code_directly(self):
desc = self._cycle()
rep = {"x.com.samsung.da.options": ["DeviceType_0001", "Course_0E", "GMT_04"]}
path, body = desc.write_fn("90", rep)
assert path == ["course", "vs", "0"]
assert body == {"x.com.samsung.da.options": ["Course_90"]}
class TestDishwasherOptions:
def test_storm_wash_read_and_write(self):
desc = next(
e
for e in dishwasher.CYCLE_OPTIONS.entities
if e.key == "storm_wash" and isinstance(e, SwitchDesc)
)
assert desc.rep_fn is not None
assert desc.rep_fn({"x.com.samsung.da.options": ["StormWashZone_On"]}) is True
assert desc.rep_fn({"x.com.samsung.da.options": ["StormWashZone_Off"]}) is False
assert desc.write_fn is not None
result = desc.write_fn("Off", {"x.com.samsung.da.options": ["StormWashZone_On"]})
assert result is not None
path, body = result
assert path == ["course", "vs", "0"]
assert "StormWashZone_Off" in body["x.com.samsung.da.options"]
def test_auto_release_exists_only_when_field_present(self):
desc = next(
e
for e in dishwasher.CYCLE_OPTIONS.entities
if e.key == "auto_release_dry" and isinstance(e, SwitchDesc)
)
assert desc.exists_fn is not None
assert desc.exists_fn({"x.com.samsung.da.options": []}, {}) is False
assert desc.exists_fn({"x.com.samsung.da.options": ["AutoDoorRelease_On"]}, {}) is True
def test_diagnosis_status_is_a_translatable_enum():
desc = next(
e
for e in dishwasher.DIAGNOSIS.entities
if e.key == "diagnosis_status" and isinstance(e, SensorDesc)
)
assert desc.device_class == "enum"
assert desc.options == ("ready",)
assert desc.value_fn("Ready") == "ready"