From 13b0bf04bb80b81a27bfb8d8311cda1d2016d870 Mon Sep 17 00:00:00 2001 From: Alex Fishlock Date: Sun, 30 Aug 2026 15:43:32 +0100 Subject: [PATCH] Migrate the Lyngdorf select platform to the lyngdorf 2.0 API (#180703) --- homeassistant/components/lyngdorf/select.py | 21 +++++++++------- tests/components/lyngdorf/conftest.py | 5 ++++ tests/components/lyngdorf/test_select.py | 28 ++++++++++++++++++--- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/lyngdorf/select.py b/homeassistant/components/lyngdorf/select.py index 04f305afafec..b2ff28a82a73 100644 --- a/homeassistant/components/lyngdorf/select.py +++ b/homeassistant/components/lyngdorf/select.py @@ -1,10 +1,10 @@ """Select platform for Lyngdorf integration.""" -from collections.abc import Callable +from collections.abc import Awaitable, Callable from dataclasses import dataclass from typing import TYPE_CHECKING, override -from lyngdorf.device import Receiver +from lyngdorf import LyngdorfReceiver from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.core import HomeAssistant @@ -21,9 +21,10 @@ PARALLEL_UPDATES = 1 class LyngdorfSelectEntityDescription(SelectEntityDescription): """Describe a Lyngdorf select entity.""" - current_option_fn: Callable[[Receiver], str | None] - options_fn: Callable[[Receiver], list[str]] - select_option_fn: Callable[[Receiver, str], None] + current_option_fn: Callable[[LyngdorfReceiver], str | None] + options_fn: Callable[[LyngdorfReceiver], list[str]] + # None on the pinned library, a coroutine on 2.x: await whichever it is. + select_option_fn: Callable[[LyngdorfReceiver, str], Awaitable[None] | None] SELECT_ENTITIES: tuple[LyngdorfSelectEntityDescription, ...] = ( @@ -31,14 +32,14 @@ SELECT_ENTITIES: tuple[LyngdorfSelectEntityDescription, ...] = ( key="room_perfect_position", translation_key="room_perfect_position", current_option_fn=lambda r: r.room_perfect_position, - options_fn=lambda r: r.available_room_perfect_positions, + options_fn=lambda r: r.room_perfect_positions, select_option_fn=lambda r, o: r.set_room_perfect_position(o), ), LyngdorfSelectEntityDescription( key="voicing", translation_key="voicing", current_option_fn=lambda r: r.voicing, - options_fn=lambda r: r.available_voicings, + options_fn=lambda r: r.voicings, select_option_fn=lambda r, o: r.set_voicing(o), ), ) @@ -67,7 +68,7 @@ class LyngdorfSelect(LyngdorfEntity, SelectEntity): def __init__( self, - receiver: Receiver, + receiver: LyngdorfReceiver, config_entry: LyngdorfConfigEntry, device_info: DeviceInfo, description: LyngdorfSelectEntityDescription, @@ -94,4 +95,6 @@ class LyngdorfSelect(LyngdorfEntity, SelectEntity): @override async def async_select_option(self, option: str) -> None: """Set the selected option.""" - self.entity_description.select_option_fn(self._receiver, option) + result = self.entity_description.select_option_fn(self._receiver, option) + if result is not None: + await result diff --git a/tests/components/lyngdorf/conftest.py b/tests/components/lyngdorf/conftest.py index c7908d283867..19d4c552b82d 100644 --- a/tests/components/lyngdorf/conftest.py +++ b/tests/components/lyngdorf/conftest.py @@ -115,8 +115,13 @@ def mock_receiver(mock_create_receiver: MagicMock) -> MagicMock: receiver.max_volume = 0.0 receiver.room_perfect_position = "Focus 1" receiver.available_room_perfect_positions = ["Global", "Focus 1"] + receiver.room_perfect_positions = ["Global", "Focus 1"] receiver.voicing = "Neutral" receiver.available_voicings = ["Neutral", "Music", "Movie"] + receiver.voicings = ["Neutral", "Music", "Movie"] + # Sync on the pinned library: they return None rather than a coroutine. + receiver.set_voicing.return_value = None + receiver.set_room_perfect_position.return_value = None receiver.lipsync = None receiver.lipsync_range = NumericRange(0, 500, 1) for _t in ("bass", "treble"): diff --git a/tests/components/lyngdorf/test_select.py b/tests/components/lyngdorf/test_select.py index cbd4526bad69..6815087cedc3 100644 --- a/tests/components/lyngdorf/test_select.py +++ b/tests/components/lyngdorf/test_select.py @@ -1,6 +1,6 @@ """Tests for the Lyngdorf select platform.""" -from unittest.mock import MagicMock +from unittest.mock import AsyncMock, MagicMock import pytest from syrupy.assertion import SnapshotAssertion @@ -46,7 +46,7 @@ async def test_room_perfect_select_option( ) -> None: """Test selecting a RoomPerfect position.""" mock_receiver.room_perfect_position = "focus" - mock_receiver.available_room_perfect_positions = ["focus", "global"] + mock_receiver.room_perfect_positions = ["focus", "global"] notify_receiver_update(mock_receiver) await hass.async_block_till_done() @@ -64,6 +64,28 @@ async def test_room_perfect_select_option( mock_receiver.set_room_perfect_position.assert_called_once_with("global") +async def test_select_option_awaits_an_awaitable_setter( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_receiver: MagicMock, +) -> None: + """Test a setter that returns a coroutine is awaited rather than dropped.""" + mock_receiver.set_voicing = AsyncMock() + mock_receiver.voicing = "Neutral" + mock_receiver.voicings = ["Neutral", "Music", "Movie"] + notify_receiver_update(mock_receiver) + await hass.async_block_till_done() + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: VOICING_ENTITY_ID, ATTR_OPTION: "Music"}, + blocking=True, + ) + + mock_receiver.set_voicing.assert_awaited_once_with("Music") + + async def test_voicing_select_option( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -71,7 +93,7 @@ async def test_voicing_select_option( ) -> None: """Test selecting a voicing.""" mock_receiver.voicing = "Neutral" - mock_receiver.available_voicings = ["Neutral", "Music", "Movie"] + mock_receiver.voicings = ["Neutral", "Music", "Movie"] notify_receiver_update(mock_receiver) await hass.async_block_till_done()