From 679c3d2bce85b53fb64e14323c70a2bb69b545aa Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 00:14:58 +0000 Subject: [PATCH] Fix remaining pre-existing ty diagnostics in test files Completes the isinstance/cast narrowing + Optional-field assert pattern across the last batch of test files. custom_components and tests are now both fully clean under ruff check, ruff format --check, and ty check. --- tests/localthings/test_config_flow.py | 3 ++- tests/test_air_purifier_capabilities.py | 12 +++++++++--- tests/test_capability.py | 8 +++++++- tests/test_dryer_capabilities.py | 4 +++- tests/test_laundry_writes.py | 26 +++++++++++++++++++------ 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/tests/localthings/test_config_flow.py b/tests/localthings/test_config_flow.py index 4dd8e2d..6bc58ba 100644 --- a/tests/localthings/test_config_flow.py +++ b/tests/localthings/test_config_flow.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Iterable, cast +from collections.abc import Iterable +from typing import cast from unittest.mock import patch from homeassistant.core import HomeAssistant diff --git a/tests/test_air_purifier_capabilities.py b/tests/test_air_purifier_capabilities.py index bfa4816..5669364 100644 --- a/tests/test_air_purifier_capabilities.py +++ b/tests/test_air_purifier_capabilities.py @@ -92,7 +92,9 @@ def test_light_switch_write_contract(): that the device merges by prefix itself, so no read-modify-write of the whole packed /mode/vs/0 options list is needed.""" desc = next( - e for e in air_purifier.MODE.entities if e.key == "display_light" and isinstance(e, SwitchDesc) + e + for e in air_purifier.MODE.entities + if e.key == "display_light" and isinstance(e, SwitchDesc) ) rep = { "x.com.samsung.da.options": [ @@ -118,7 +120,9 @@ def test_operating_mode_is_a_read_only_diagnostic(): guess that it was the fan-speed selector; its real purpose is still unconfirmed (see the air_purifier.py module docstring).""" operating_mode = next( - e for e in air_purifier.MODE.entities if e.key == "operating_mode" and isinstance(e, SensorDesc) + e + for e in air_purifier.MODE.entities + if e.key == "operating_mode" and isinstance(e, SensorDesc) ) rep = {"x.com.samsung.da.options": ["Comode_Off"]} assert operating_mode.rep_fn is not None @@ -154,7 +158,9 @@ def test_airflow_fan_write_contract(): is a clean, monotonic 0-4 code, so the write is a plain int passthrough -- no named-preset table needed (see fan.py's LocalThingsAirflowFan).""" fan_desc = next( - e for e in air_purifier.AIRFLOW_GENERIC.entities if e.key == "airflow_fan" and isinstance(e, FanDesc) + e + for e in air_purifier.AIRFLOW_GENERIC.entities + if e.key == "airflow_fan" and isinstance(e, FanDesc) ) assert fan_desc.write_fn is not None assert fan_desc.write_fn(("speed", 3), {}) == (["airflow", "0"], {"speed": 3}) diff --git a/tests/test_capability.py b/tests/test_capability.py index bef599e..e4a06b4 100644 --- a/tests/test_capability.py +++ b/tests/test_capability.py @@ -13,8 +13,14 @@ def test_capability_defaults(): def test_capability_is_frozen(): c = Capability(href="/kidslock/vs/0", entities=()) + # setattr(), not `c.href = ...` -- `href` is a read-only property to the + # type checker (frozen dataclass field), so a literal attribute + # assignment doesn't type-check; setattr's signature is untyped enough + # to accept it, and it still goes through the same frozen __setattr__ + # at runtime, so this is still a faithful test of the frozen guarantee. + attr_name = "href" try: - setattr(c, "href", "/other/vs/0") + setattr(c, attr_name, "/other/vs/0") except Exception: return raise AssertionError("expected frozen dataclass") diff --git a/tests/test_dryer_capabilities.py b/tests/test_dryer_capabilities.py index e82fd23..1791efb 100644 --- a/tests/test_dryer_capabilities.py +++ b/tests/test_dryer_capabilities.py @@ -75,7 +75,9 @@ def test_course_bound_to_shared_course_vs_0(): the shipped dryer_cycle_table_03 translations, consistent with washer/dishwasher.""" assert dryer.DRYER_COURSE.href == "/course/vs/0" - desc = next(e for e in dryer.DRYER_COURSE.entities if e.key == "cycle" and isinstance(e, SelectDesc)) + desc = next( + e for e in dryer.DRYER_COURSE.entities if e.key == "cycle" and isinstance(e, SelectDesc) + ) assert callable(desc.translation_key) table_03 = {"/st/dryercourse/vs/0": {"x.com.samsung.da.st.courseTable": "Table_03"}} assert desc.translation_key(table_03) == "dryer_cycle_table_03" diff --git a/tests/test_laundry_writes.py b/tests/test_laundry_writes.py index 11e9ce8..a4314a3 100644 --- a/tests/test_laundry_writes.py +++ b/tests/test_laundry_writes.py @@ -6,7 +6,9 @@ from custom_components.localthings.registry.entities import ButtonDesc, SelectDe def test_start_button_writes_run(): - btn = next(e for e in OPERATIONAL_STATE.entities if e.key == "start" and isinstance(e, ButtonDesc)) + btn = next( + e for e in OPERATIONAL_STATE.entities if e.key == "start" and isinstance(e, ButtonDesc) + ) assert btn.write_fn is not None assert btn.write_fn("Run", {}) == ( ["operational", "state", "vs", "0"], @@ -15,7 +17,9 @@ def test_start_button_writes_run(): def test_pause_button_writes_pause(): - btn = next(e for e in OPERATIONAL_STATE.entities if e.key == "pause" and isinstance(e, ButtonDesc)) + btn = next( + e for e in OPERATIONAL_STATE.entities if e.key == "pause" and isinstance(e, ButtonDesc) + ) assert btn.write_fn is not None assert btn.write_fn("Pause", {}) == ( ["operational", "state", "vs", "0"], @@ -24,7 +28,9 @@ def test_pause_button_writes_pause(): def test_stop_button_writes_ready(): - btn = next(e for e in OPERATIONAL_STATE.entities if e.key == "stop" and isinstance(e, ButtonDesc)) + btn = next( + e for e in OPERATIONAL_STATE.entities if e.key == "stop" and isinstance(e, ButtonDesc) + ) assert btn.write_fn is not None assert btn.write_fn("Ready", {}) == ( ["operational", "state", "vs", "0"], @@ -33,7 +39,11 @@ def test_stop_button_writes_ready(): def test_sound_mode_write_valid(): - desc = next(e for e in laundry.SOUND_MODE.entities if e.key == "sound_mode" and isinstance(e, SelectDesc)) + desc = next( + e + for e in laundry.SOUND_MODE.entities + if e.key == "sound_mode" and isinstance(e, SelectDesc) + ) assert desc.write_fn is not None assert desc.write_fn("mute", {}) == ( ["settings", "sound", "mode", "vs", "0"], @@ -44,7 +54,9 @@ def test_sound_mode_write_valid(): def test_led_brightness_write_valid(): desc = next( - e for e in laundry.DOOR_LED.entities if e.key == "led_brightness" and isinstance(e, SelectDesc) + e + for e in laundry.DOOR_LED.entities + if e.key == "led_brightness" and isinstance(e, SelectDesc) ) assert desc.write_fn is not None assert desc.write_fn("Low", {}) == ( @@ -56,7 +68,9 @@ def test_led_brightness_write_valid(): def test_led_night_light_write_valid(): desc = next( - e for e in laundry.DOOR_LED.entities if e.key == "led_night_light" and isinstance(e, SwitchDesc) + e + for e in laundry.DOOR_LED.entities + if e.key == "led_night_light" and isinstance(e, SwitchDesc) ) assert desc.write_fn is not None assert desc.write_fn("On", {}) == (