55import pytest
66from pyvlx .opening_device import Awning , GarageDoor , Gate , RollerShutter , Window
77
8+ from homeassistant .components .cover import (
9+ ATTR_POSITION ,
10+ ATTR_TILT_POSITION ,
11+ DOMAIN as COVER_DOMAIN ,
12+ SERVICE_CLOSE_COVER ,
13+ SERVICE_CLOSE_COVER_TILT ,
14+ SERVICE_OPEN_COVER ,
15+ SERVICE_OPEN_COVER_TILT ,
16+ SERVICE_SET_COVER_POSITION ,
17+ SERVICE_SET_COVER_TILT_POSITION ,
18+ SERVICE_STOP_COVER ,
19+ SERVICE_STOP_COVER_TILT ,
20+ )
821from homeassistant .components .velux import DOMAIN
9- from homeassistant .const import STATE_CLOSED , STATE_OPEN , Platform
22+ from homeassistant .const import (
23+ STATE_CLOSED ,
24+ STATE_CLOSING ,
25+ STATE_OPEN ,
26+ STATE_OPENING ,
27+ Platform ,
28+ )
1029from homeassistant .core import HomeAssistant
1130from homeassistant .helpers import device_registry as dr , entity_registry as er
1231
1332from . import update_callback_entity
1433
1534from tests .common import MockConfigEntry , SnapshotAssertion , snapshot_platform
1635
36+ # Apply setup_integration fixture to all tests in this module
37+ pytestmark = pytest .mark .usefixtures ("setup_integration" )
38+
1739
1840@pytest .fixture
1941def platform () -> Platform :
2042 """Fixture to specify platform to test."""
2143 return Platform .COVER
2244
2345
24- @pytest .mark .usefixtures ("setup_integration" )
2546@pytest .mark .parametrize ("mock_pyvlx" , ["mock_blind" ], indirect = True )
2647async def test_blind_entity_setup (
2748 hass : HomeAssistant ,
@@ -38,7 +59,6 @@ async def test_blind_entity_setup(
3859 )
3960
4061
41- @pytest .mark .usefixtures ("setup_integration" )
4262@pytest .mark .usefixtures ("mock_cover_type" )
4363@pytest .mark .parametrize (
4464 "mock_cover_type" , [Awning , GarageDoor , Gate , RollerShutter , Window ], indirect = True
@@ -63,7 +83,6 @@ async def test_cover_entity_setup(
6383 )
6484
6585
66- @pytest .mark .usefixtures ("setup_integration" )
6786async def test_cover_device_association (
6887 hass : HomeAssistant ,
6988 mock_config_entry : MockConfigEntry ,
@@ -91,7 +110,6 @@ async def test_cover_device_association(
91110 ) in via_device_entry .identifiers
92111
93112
94- @pytest .mark .usefixtures ("setup_integration" )
95113async def test_cover_closed (
96114 hass : HomeAssistant ,
97115 mock_window : AsyncMock ,
@@ -117,3 +135,164 @@ async def test_cover_closed(
117135 state = hass .states .get (test_entity_id )
118136 assert state is not None
119137 assert state .state == STATE_CLOSED
138+
139+
140+ # Window command tests
141+
142+
143+ async def test_window_open_close_stop_services (
144+ hass : HomeAssistant , mock_window : AsyncMock
145+ ) -> None :
146+ """Verify open/close/stop services map to device calls with no wait."""
147+
148+ entity_id = "cover.test_window"
149+
150+ await hass .services .async_call (
151+ COVER_DOMAIN , SERVICE_OPEN_COVER , {"entity_id" : entity_id }, blocking = True
152+ )
153+ mock_window .open .assert_awaited_once_with (wait_for_completion = False )
154+
155+ await hass .services .async_call (
156+ COVER_DOMAIN , SERVICE_CLOSE_COVER , {"entity_id" : entity_id }, blocking = True
157+ )
158+ mock_window .close .assert_awaited_once_with (wait_for_completion = False )
159+
160+ await hass .services .async_call (
161+ COVER_DOMAIN , SERVICE_STOP_COVER , {"entity_id" : entity_id }, blocking = True
162+ )
163+ mock_window .stop .assert_awaited_once_with (wait_for_completion = False )
164+
165+
166+ async def test_window_set_cover_position_inversion (
167+ hass : HomeAssistant , mock_window : AsyncMock
168+ ) -> None :
169+ """HA position is inverted for device's Position."""
170+
171+ entity_id = "cover.test_window"
172+
173+ # Call with position 30 (=70% for device)
174+ await hass .services .async_call (
175+ COVER_DOMAIN ,
176+ SERVICE_SET_COVER_POSITION ,
177+ {"entity_id" : entity_id , ATTR_POSITION : 30 },
178+ blocking = True ,
179+ )
180+
181+ # Expect device Position 70%
182+ args , kwargs = mock_window .set_position .await_args
183+ position_obj = args [0 ]
184+ assert position_obj .position_percent == 70
185+ assert kwargs .get ("wait_for_completion" ) is False
186+
187+
188+ async def test_window_current_position_and_opening_closing_states (
189+ hass : HomeAssistant , mock_window : AsyncMock
190+ ) -> None :
191+ """Validate current_position and opening/closing state transitions."""
192+
193+ entity_id = "cover.test_window"
194+
195+ # device position 30 -> current_position 70
196+ mock_window .position .position_percent = 30
197+ await update_callback_entity (hass , mock_window )
198+ state = hass .states .get (entity_id )
199+ assert state is not None
200+ assert state .attributes .get ("current_position" ) == 70
201+ assert state .state == STATE_OPEN
202+
203+ # Opening
204+ mock_window .is_opening = True
205+ mock_window .is_closing = False
206+ await update_callback_entity (hass , mock_window )
207+ state = hass .states .get (entity_id )
208+ assert state is not None
209+ assert state .state == STATE_OPENING
210+
211+ # Closing
212+ mock_window .is_opening = False
213+ mock_window .is_closing = True
214+ await update_callback_entity (hass , mock_window )
215+ state = hass .states .get (entity_id )
216+ assert state is not None
217+ assert state .state == STATE_CLOSING
218+
219+
220+ # Blind command tests
221+
222+
223+ async def test_blind_open_close_stop_tilt_services (
224+ hass : HomeAssistant , mock_blind : AsyncMock
225+ ) -> None :
226+ """Verify tilt services map to orientation calls."""
227+
228+ entity_id = "cover.test_blind"
229+
230+ await hass .services .async_call (
231+ COVER_DOMAIN ,
232+ SERVICE_OPEN_COVER_TILT ,
233+ {"entity_id" : entity_id },
234+ blocking = True ,
235+ )
236+ mock_blind .open_orientation .assert_awaited_once_with (wait_for_completion = False )
237+
238+ await hass .services .async_call (
239+ COVER_DOMAIN ,
240+ SERVICE_CLOSE_COVER_TILT ,
241+ {"entity_id" : entity_id },
242+ blocking = True ,
243+ )
244+ mock_blind .close_orientation .assert_awaited_once_with (wait_for_completion = False )
245+
246+ await hass .services .async_call (
247+ COVER_DOMAIN ,
248+ SERVICE_STOP_COVER_TILT ,
249+ {"entity_id" : entity_id },
250+ blocking = True ,
251+ )
252+ mock_blind .stop_orientation .assert_awaited_once_with (wait_for_completion = False )
253+
254+
255+ async def test_blind_set_cover_tilt_position_inversion (
256+ hass : HomeAssistant , mock_blind : AsyncMock
257+ ) -> None :
258+ """HA tilt position is inverted for device orientation."""
259+
260+ entity_id = "cover.test_blind"
261+
262+ await hass .services .async_call (
263+ COVER_DOMAIN ,
264+ SERVICE_SET_COVER_TILT_POSITION ,
265+ {"entity_id" : entity_id , ATTR_TILT_POSITION : 25 },
266+ blocking = True ,
267+ )
268+
269+ call = mock_blind .set_orientation .await_args
270+ orientation_obj = call .kwargs .get ("orientation" )
271+ assert orientation_obj is not None
272+ assert orientation_obj .position_percent == 75
273+ assert call .kwargs .get ("wait_for_completion" ) is False
274+
275+
276+ async def test_blind_current_tilt_position (
277+ hass : HomeAssistant , mock_blind : AsyncMock
278+ ) -> None :
279+ """Validate current_tilt_position attribute reflects inverted orientation."""
280+
281+ entity_id = "cover.test_blind"
282+ mock_blind .orientation .position_percent = 10
283+ await update_callback_entity (hass , mock_blind )
284+ state = hass .states .get (entity_id )
285+ assert state is not None
286+ assert state .attributes .get ("current_tilt_position" ) == 90
287+
288+
289+ async def test_non_blind_has_no_tilt_position (
290+ hass : HomeAssistant , mock_window : AsyncMock
291+ ) -> None :
292+ """Non-blind covers should not expose current_tilt_position attribute."""
293+
294+ entity_id = "cover.test_window"
295+ await update_callback_entity (hass , mock_window )
296+ state = hass .states .get (entity_id )
297+ assert state is not None
298+ assert "current_tilt_position" not in state .attributes
0 commit comments