pytest-homeassistant-custom-component 0.13.308__py3-none-any.whl → 0.13.309__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_homeassistant_custom_component/common.py +1 -0
- pytest_homeassistant_custom_component/components/__init__.py +383 -21
- pytest_homeassistant_custom_component/const.py +2 -2
- {pytest_homeassistant_custom_component-0.13.308.dist-info → pytest_homeassistant_custom_component-0.13.309.dist-info}/METADATA +4 -3
- {pytest_homeassistant_custom_component-0.13.308.dist-info → pytest_homeassistant_custom_component-0.13.309.dist-info}/RECORD +10 -10
- {pytest_homeassistant_custom_component-0.13.308.dist-info → pytest_homeassistant_custom_component-0.13.309.dist-info}/WHEEL +1 -1
- {pytest_homeassistant_custom_component-0.13.308.dist-info → pytest_homeassistant_custom_component-0.13.309.dist-info}/entry_points.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.308.dist-info → pytest_homeassistant_custom_component-0.13.309.dist-info}/licenses/LICENSE +0 -0
- {pytest_homeassistant_custom_component-0.13.308.dist-info → pytest_homeassistant_custom_component-0.13.309.dist-info}/licenses/LICENSE_HA_CORE.md +0 -0
- {pytest_homeassistant_custom_component-0.13.308.dist-info → pytest_homeassistant_custom_component-0.13.309.dist-info}/top_level.txt +0 -0
|
@@ -705,6 +705,7 @@ class RegistryEntryWithDefaults(er.RegistryEntry):
|
|
|
705
705
|
converter=attr.converters.default_if_none(factory=uuid_util.random_uuid_hex), # type: ignore[misc]
|
|
706
706
|
)
|
|
707
707
|
has_entity_name: bool = attr.ib(default=False)
|
|
708
|
+
object_id_base: str | None = attr.ib(default=None)
|
|
708
709
|
options: er.ReadOnlyEntityOptionsType = attr.ib(
|
|
709
710
|
default=None, converter=er._protect_entity_options
|
|
710
711
|
)
|
|
@@ -7,13 +7,18 @@ This file is originally from homeassistant/core and modified by pytest-homeassis
|
|
|
7
7
|
from collections.abc import Iterable
|
|
8
8
|
from enum import StrEnum
|
|
9
9
|
import itertools
|
|
10
|
-
from typing import TypedDict
|
|
10
|
+
from typing import Any, TypedDict
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
11
13
|
|
|
12
14
|
from homeassistant.const import (
|
|
13
15
|
ATTR_AREA_ID,
|
|
14
16
|
ATTR_DEVICE_ID,
|
|
15
17
|
ATTR_FLOOR_ID,
|
|
16
18
|
ATTR_LABEL_ID,
|
|
19
|
+
CONF_ABOVE,
|
|
20
|
+
CONF_BELOW,
|
|
21
|
+
CONF_CONDITION,
|
|
17
22
|
CONF_ENTITY_ID,
|
|
18
23
|
CONF_OPTIONS,
|
|
19
24
|
CONF_PLATFORM,
|
|
@@ -29,6 +34,16 @@ from homeassistant.helpers import (
|
|
|
29
34
|
floor_registry as fr,
|
|
30
35
|
label_registry as lr,
|
|
31
36
|
)
|
|
37
|
+
from homeassistant.helpers.condition import (
|
|
38
|
+
ConditionCheckerTypeOptional,
|
|
39
|
+
async_from_config as async_condition_from_config,
|
|
40
|
+
)
|
|
41
|
+
from homeassistant.helpers.trigger import (
|
|
42
|
+
CONF_LOWER_LIMIT,
|
|
43
|
+
CONF_THRESHOLD_TYPE,
|
|
44
|
+
CONF_UPPER_LIMIT,
|
|
45
|
+
ThresholdType,
|
|
46
|
+
)
|
|
32
47
|
from homeassistant.setup import async_setup_component
|
|
33
48
|
|
|
34
49
|
from ..common import MockConfigEntry, mock_device_registry
|
|
@@ -89,6 +104,13 @@ async def target_entities(
|
|
|
89
104
|
suggested_object_id=f"device_{domain}",
|
|
90
105
|
device_id=device.id,
|
|
91
106
|
)
|
|
107
|
+
entity_reg.async_get_or_create(
|
|
108
|
+
domain=domain,
|
|
109
|
+
platform="test",
|
|
110
|
+
unique_id=f"{domain}_device2",
|
|
111
|
+
suggested_object_id=f"device2_{domain}",
|
|
112
|
+
device_id=device.id,
|
|
113
|
+
)
|
|
92
114
|
entity_reg.async_get_or_create(
|
|
93
115
|
domain=domain,
|
|
94
116
|
platform="test",
|
|
@@ -119,9 +141,11 @@ async def target_entities(
|
|
|
119
141
|
return {
|
|
120
142
|
"included": [
|
|
121
143
|
f"{domain}.standalone_{domain}",
|
|
144
|
+
f"{domain}.standalone2_{domain}",
|
|
122
145
|
f"{domain}.label_{domain}",
|
|
123
146
|
f"{domain}.area_{domain}",
|
|
124
147
|
f"{domain}.device_{domain}",
|
|
148
|
+
f"{domain}.device2_{domain}",
|
|
125
149
|
],
|
|
126
150
|
"excluded": [
|
|
127
151
|
f"{domain}.standalone_{domain}_excluded",
|
|
@@ -139,44 +163,206 @@ def parametrize_target_entities(domain: str) -> list[tuple[dict, str, int]]:
|
|
|
139
163
|
"""
|
|
140
164
|
return [
|
|
141
165
|
(
|
|
142
|
-
{
|
|
166
|
+
{
|
|
167
|
+
CONF_ENTITY_ID: [
|
|
168
|
+
f"{domain}.standalone_{domain}",
|
|
169
|
+
f"{domain}.standalone2_{domain}",
|
|
170
|
+
]
|
|
171
|
+
},
|
|
143
172
|
f"{domain}.standalone_{domain}",
|
|
144
|
-
|
|
173
|
+
2,
|
|
145
174
|
),
|
|
146
|
-
({ATTR_LABEL_ID: "test_label"}, f"{domain}.label_{domain}",
|
|
147
|
-
({ATTR_AREA_ID: "test_area"}, f"{domain}.area_{domain}",
|
|
148
|
-
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.area_{domain}",
|
|
149
|
-
({ATTR_LABEL_ID: "test_label"}, f"{domain}.device_{domain}",
|
|
150
|
-
({ATTR_AREA_ID: "test_area"}, f"{domain}.device_{domain}",
|
|
151
|
-
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.device_{domain}",
|
|
152
|
-
({ATTR_DEVICE_ID: "test_device"}, f"{domain}.device_{domain}",
|
|
175
|
+
({ATTR_LABEL_ID: "test_label"}, f"{domain}.label_{domain}", 3),
|
|
176
|
+
({ATTR_AREA_ID: "test_area"}, f"{domain}.area_{domain}", 3),
|
|
177
|
+
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.area_{domain}", 3),
|
|
178
|
+
({ATTR_LABEL_ID: "test_label"}, f"{domain}.device_{domain}", 3),
|
|
179
|
+
({ATTR_AREA_ID: "test_area"}, f"{domain}.device_{domain}", 3),
|
|
180
|
+
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.device_{domain}", 3),
|
|
181
|
+
({ATTR_DEVICE_ID: "test_device"}, f"{domain}.device_{domain}", 2),
|
|
153
182
|
]
|
|
154
183
|
|
|
155
184
|
|
|
156
185
|
class _StateDescription(TypedDict):
|
|
157
|
-
"""Test state
|
|
186
|
+
"""Test state with attributes."""
|
|
158
187
|
|
|
159
188
|
state: str | None
|
|
160
189
|
attributes: dict
|
|
161
190
|
|
|
162
191
|
|
|
163
|
-
class
|
|
192
|
+
class TriggerStateDescription(TypedDict):
|
|
164
193
|
"""Test state and expected service call count."""
|
|
165
194
|
|
|
166
|
-
included: _StateDescription
|
|
167
|
-
excluded: _StateDescription
|
|
168
|
-
count: int
|
|
195
|
+
included: _StateDescription # State for entities meant to be targeted
|
|
196
|
+
excluded: _StateDescription # State for entities not meant to be targeted
|
|
197
|
+
count: int # Expected service call count
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ConditionStateDescription(TypedDict):
|
|
201
|
+
"""Test state and expected condition evaluation."""
|
|
202
|
+
|
|
203
|
+
included: _StateDescription # State for entities meant to be targeted
|
|
204
|
+
excluded: _StateDescription # State for entities not meant to be targeted
|
|
205
|
+
|
|
206
|
+
condition_true: bool # If the condition is expected to evaluate to true
|
|
207
|
+
condition_true_first_entity: bool # If the condition is expected to evaluate to true for the first targeted entity
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _parametrize_condition_states(
|
|
211
|
+
*,
|
|
212
|
+
condition: str,
|
|
213
|
+
condition_options: dict[str, Any] | None = None,
|
|
214
|
+
target_states: list[str | None | tuple[str | None, dict]],
|
|
215
|
+
other_states: list[str | None | tuple[str | None, dict]],
|
|
216
|
+
additional_attributes: dict | None,
|
|
217
|
+
condition_true_if_invalid: bool,
|
|
218
|
+
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
|
219
|
+
"""Parametrize states and expected condition evaluations.
|
|
220
|
+
|
|
221
|
+
The target_states and other_states iterables are either iterables of
|
|
222
|
+
states or iterables of (state, attributes) tuples.
|
|
223
|
+
|
|
224
|
+
Returns a list of tuples with (condition, condition options, list of states),
|
|
225
|
+
where states is a list of ConditionStateDescription dicts.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
additional_attributes = additional_attributes or {}
|
|
229
|
+
condition_options = condition_options or {}
|
|
230
|
+
|
|
231
|
+
def state_with_attributes(
|
|
232
|
+
state: str | None | tuple[str | None, dict],
|
|
233
|
+
condition_true: bool,
|
|
234
|
+
condition_true_first_entity: bool,
|
|
235
|
+
) -> ConditionStateDescription:
|
|
236
|
+
"""Return ConditionStateDescription dict."""
|
|
237
|
+
if isinstance(state, str) or state is None:
|
|
238
|
+
return {
|
|
239
|
+
"included": {
|
|
240
|
+
"state": state,
|
|
241
|
+
"attributes": additional_attributes,
|
|
242
|
+
},
|
|
243
|
+
"excluded": {
|
|
244
|
+
"state": state,
|
|
245
|
+
"attributes": {},
|
|
246
|
+
},
|
|
247
|
+
"condition_true": condition_true,
|
|
248
|
+
"condition_true_first_entity": condition_true_first_entity,
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
"included": {
|
|
252
|
+
"state": state[0],
|
|
253
|
+
"attributes": state[1] | additional_attributes,
|
|
254
|
+
},
|
|
255
|
+
"excluded": {
|
|
256
|
+
"state": state[0],
|
|
257
|
+
"attributes": state[1],
|
|
258
|
+
},
|
|
259
|
+
"condition_true": condition_true,
|
|
260
|
+
"condition_true_first_entity": condition_true_first_entity,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return [
|
|
264
|
+
(
|
|
265
|
+
condition,
|
|
266
|
+
condition_options,
|
|
267
|
+
list(
|
|
268
|
+
itertools.chain(
|
|
269
|
+
(state_with_attributes(None, condition_true_if_invalid, True),),
|
|
270
|
+
(
|
|
271
|
+
state_with_attributes(
|
|
272
|
+
STATE_UNAVAILABLE, condition_true_if_invalid, True
|
|
273
|
+
),
|
|
274
|
+
),
|
|
275
|
+
(
|
|
276
|
+
state_with_attributes(
|
|
277
|
+
STATE_UNKNOWN, condition_true_if_invalid, True
|
|
278
|
+
),
|
|
279
|
+
),
|
|
280
|
+
(
|
|
281
|
+
state_with_attributes(other_state, False, False)
|
|
282
|
+
for other_state in other_states
|
|
283
|
+
),
|
|
284
|
+
),
|
|
285
|
+
),
|
|
286
|
+
),
|
|
287
|
+
# Test each target state individually to isolate condition_true expectations
|
|
288
|
+
*(
|
|
289
|
+
(
|
|
290
|
+
condition,
|
|
291
|
+
condition_options,
|
|
292
|
+
[
|
|
293
|
+
state_with_attributes(other_states[0], False, False),
|
|
294
|
+
state_with_attributes(target_state, True, False),
|
|
295
|
+
],
|
|
296
|
+
)
|
|
297
|
+
for target_state in target_states
|
|
298
|
+
),
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def parametrize_condition_states_any(
|
|
303
|
+
*,
|
|
304
|
+
condition: str,
|
|
305
|
+
condition_options: dict[str, Any] | None = None,
|
|
306
|
+
target_states: list[str | None | tuple[str | None, dict]],
|
|
307
|
+
other_states: list[str | None | tuple[str | None, dict]],
|
|
308
|
+
additional_attributes: dict | None = None,
|
|
309
|
+
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
|
310
|
+
"""Parametrize states and expected condition evaluations.
|
|
311
|
+
|
|
312
|
+
The target_states and other_states iterables are either iterables of
|
|
313
|
+
states or iterables of (state, attributes) tuples.
|
|
314
|
+
|
|
315
|
+
Returns a list of tuples with (condition, condition options, list of states),
|
|
316
|
+
where states is a list of ConditionStateDescription dicts.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
return _parametrize_condition_states(
|
|
320
|
+
condition=condition,
|
|
321
|
+
condition_options=condition_options,
|
|
322
|
+
target_states=target_states,
|
|
323
|
+
other_states=other_states,
|
|
324
|
+
additional_attributes=additional_attributes,
|
|
325
|
+
condition_true_if_invalid=False,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def parametrize_condition_states_all(
|
|
330
|
+
*,
|
|
331
|
+
condition: str,
|
|
332
|
+
condition_options: dict[str, Any] | None = None,
|
|
333
|
+
target_states: list[str | None | tuple[str | None, dict]],
|
|
334
|
+
other_states: list[str | None | tuple[str | None, dict]],
|
|
335
|
+
additional_attributes: dict | None = None,
|
|
336
|
+
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
|
337
|
+
"""Parametrize states and expected condition evaluations.
|
|
338
|
+
|
|
339
|
+
The target_states and other_states iterables are either iterables of
|
|
340
|
+
states or iterables of (state, attributes) tuples.
|
|
341
|
+
|
|
342
|
+
Returns a list of tuples with (condition, condition options, list of states),
|
|
343
|
+
where states is a list of ConditionStateDescription dicts.
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
return _parametrize_condition_states(
|
|
347
|
+
condition=condition,
|
|
348
|
+
condition_options=condition_options,
|
|
349
|
+
target_states=target_states,
|
|
350
|
+
other_states=other_states,
|
|
351
|
+
additional_attributes=additional_attributes,
|
|
352
|
+
condition_true_if_invalid=True,
|
|
353
|
+
)
|
|
169
354
|
|
|
170
355
|
|
|
171
356
|
def parametrize_trigger_states(
|
|
172
357
|
*,
|
|
173
358
|
trigger: str,
|
|
359
|
+
trigger_options: dict[str, Any] | None = None,
|
|
174
360
|
target_states: list[str | None | tuple[str | None, dict]],
|
|
175
361
|
other_states: list[str | None | tuple[str | None, dict]],
|
|
176
362
|
additional_attributes: dict | None = None,
|
|
177
363
|
trigger_from_none: bool = True,
|
|
178
364
|
retrigger_on_target_state: bool = False,
|
|
179
|
-
) -> list[tuple[str, list[
|
|
365
|
+
) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
|
|
180
366
|
"""Parametrize states and expected service call counts.
|
|
181
367
|
|
|
182
368
|
The target_states and other_states iterables are either iterables of
|
|
@@ -189,15 +375,16 @@ def parametrize_trigger_states(
|
|
|
189
375
|
when the state changes to another target state.
|
|
190
376
|
|
|
191
377
|
Returns a list of tuples with (trigger, list of states),
|
|
192
|
-
where states is a list of
|
|
378
|
+
where states is a list of TriggerStateDescription dicts.
|
|
193
379
|
"""
|
|
194
380
|
|
|
195
381
|
additional_attributes = additional_attributes or {}
|
|
382
|
+
trigger_options = trigger_options or {}
|
|
196
383
|
|
|
197
384
|
def state_with_attributes(
|
|
198
385
|
state: str | None | tuple[str | None, dict], count: int
|
|
199
|
-
) ->
|
|
200
|
-
"""Return
|
|
386
|
+
) -> TriggerStateDescription:
|
|
387
|
+
"""Return TriggerStateDescription dict."""
|
|
201
388
|
if isinstance(state, str) or state is None:
|
|
202
389
|
return {
|
|
203
390
|
"included": {
|
|
@@ -226,6 +413,7 @@ def parametrize_trigger_states(
|
|
|
226
413
|
# Initial state None
|
|
227
414
|
(
|
|
228
415
|
trigger,
|
|
416
|
+
trigger_options,
|
|
229
417
|
list(
|
|
230
418
|
itertools.chain.from_iterable(
|
|
231
419
|
(
|
|
@@ -244,6 +432,7 @@ def parametrize_trigger_states(
|
|
|
244
432
|
# Initial state different from target state
|
|
245
433
|
(
|
|
246
434
|
trigger,
|
|
435
|
+
trigger_options,
|
|
247
436
|
# other_state,
|
|
248
437
|
list(
|
|
249
438
|
itertools.chain.from_iterable(
|
|
@@ -261,6 +450,7 @@ def parametrize_trigger_states(
|
|
|
261
450
|
# Initial state same as target state
|
|
262
451
|
(
|
|
263
452
|
trigger,
|
|
453
|
+
trigger_options,
|
|
264
454
|
list(
|
|
265
455
|
itertools.chain.from_iterable(
|
|
266
456
|
(
|
|
@@ -280,6 +470,7 @@ def parametrize_trigger_states(
|
|
|
280
470
|
# Initial state unavailable / unknown
|
|
281
471
|
(
|
|
282
472
|
trigger,
|
|
473
|
+
trigger_options,
|
|
283
474
|
list(
|
|
284
475
|
itertools.chain.from_iterable(
|
|
285
476
|
(
|
|
@@ -295,6 +486,7 @@ def parametrize_trigger_states(
|
|
|
295
486
|
),
|
|
296
487
|
(
|
|
297
488
|
trigger,
|
|
489
|
+
trigger_options,
|
|
298
490
|
list(
|
|
299
491
|
itertools.chain.from_iterable(
|
|
300
492
|
(
|
|
@@ -315,6 +507,7 @@ def parametrize_trigger_states(
|
|
|
315
507
|
tests.append(
|
|
316
508
|
(
|
|
317
509
|
trigger,
|
|
510
|
+
trigger_options,
|
|
318
511
|
list(
|
|
319
512
|
itertools.chain.from_iterable(
|
|
320
513
|
(
|
|
@@ -339,10 +532,127 @@ def parametrize_trigger_states(
|
|
|
339
532
|
return tests
|
|
340
533
|
|
|
341
534
|
|
|
535
|
+
def parametrize_numerical_attribute_changed_trigger_states(
|
|
536
|
+
trigger: str, state: str, attribute: str
|
|
537
|
+
) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
|
|
538
|
+
"""Parametrize states and expected service call counts for numerical changed triggers."""
|
|
539
|
+
return [
|
|
540
|
+
*parametrize_trigger_states(
|
|
541
|
+
trigger=trigger,
|
|
542
|
+
trigger_options={},
|
|
543
|
+
target_states=[
|
|
544
|
+
(state, {attribute: 0}),
|
|
545
|
+
(state, {attribute: 50}),
|
|
546
|
+
(state, {attribute: 100}),
|
|
547
|
+
],
|
|
548
|
+
other_states=[(state, {attribute: None})],
|
|
549
|
+
retrigger_on_target_state=True,
|
|
550
|
+
),
|
|
551
|
+
*parametrize_trigger_states(
|
|
552
|
+
trigger=trigger,
|
|
553
|
+
trigger_options={CONF_ABOVE: 10},
|
|
554
|
+
target_states=[
|
|
555
|
+
(state, {attribute: 50}),
|
|
556
|
+
(state, {attribute: 100}),
|
|
557
|
+
],
|
|
558
|
+
other_states=[
|
|
559
|
+
(state, {attribute: None}),
|
|
560
|
+
(state, {attribute: 0}),
|
|
561
|
+
],
|
|
562
|
+
retrigger_on_target_state=True,
|
|
563
|
+
),
|
|
564
|
+
*parametrize_trigger_states(
|
|
565
|
+
trigger=trigger,
|
|
566
|
+
trigger_options={CONF_BELOW: 90},
|
|
567
|
+
target_states=[
|
|
568
|
+
(state, {attribute: 0}),
|
|
569
|
+
(state, {attribute: 50}),
|
|
570
|
+
],
|
|
571
|
+
other_states=[
|
|
572
|
+
(state, {attribute: None}),
|
|
573
|
+
(state, {attribute: 100}),
|
|
574
|
+
],
|
|
575
|
+
retrigger_on_target_state=True,
|
|
576
|
+
),
|
|
577
|
+
]
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def parametrize_numerical_attribute_crossed_threshold_trigger_states(
|
|
581
|
+
trigger: str, state: str, attribute: str
|
|
582
|
+
) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
|
|
583
|
+
"""Parametrize states and expected service call counts for numerical crossed threshold triggers."""
|
|
584
|
+
return [
|
|
585
|
+
*parametrize_trigger_states(
|
|
586
|
+
trigger=trigger,
|
|
587
|
+
trigger_options={
|
|
588
|
+
CONF_THRESHOLD_TYPE: ThresholdType.BETWEEN,
|
|
589
|
+
CONF_LOWER_LIMIT: 10,
|
|
590
|
+
CONF_UPPER_LIMIT: 90,
|
|
591
|
+
},
|
|
592
|
+
target_states=[
|
|
593
|
+
(state, {attribute: 50}),
|
|
594
|
+
(state, {attribute: 60}),
|
|
595
|
+
],
|
|
596
|
+
other_states=[
|
|
597
|
+
(state, {attribute: None}),
|
|
598
|
+
(state, {attribute: 0}),
|
|
599
|
+
(state, {attribute: 100}),
|
|
600
|
+
],
|
|
601
|
+
),
|
|
602
|
+
*parametrize_trigger_states(
|
|
603
|
+
trigger=trigger,
|
|
604
|
+
trigger_options={
|
|
605
|
+
CONF_THRESHOLD_TYPE: ThresholdType.OUTSIDE,
|
|
606
|
+
CONF_LOWER_LIMIT: 10,
|
|
607
|
+
CONF_UPPER_LIMIT: 90,
|
|
608
|
+
},
|
|
609
|
+
target_states=[
|
|
610
|
+
(state, {attribute: 0}),
|
|
611
|
+
(state, {attribute: 100}),
|
|
612
|
+
],
|
|
613
|
+
other_states=[
|
|
614
|
+
(state, {attribute: None}),
|
|
615
|
+
(state, {attribute: 50}),
|
|
616
|
+
(state, {attribute: 60}),
|
|
617
|
+
],
|
|
618
|
+
),
|
|
619
|
+
*parametrize_trigger_states(
|
|
620
|
+
trigger=trigger,
|
|
621
|
+
trigger_options={
|
|
622
|
+
CONF_THRESHOLD_TYPE: ThresholdType.ABOVE,
|
|
623
|
+
CONF_LOWER_LIMIT: 10,
|
|
624
|
+
},
|
|
625
|
+
target_states=[
|
|
626
|
+
(state, {attribute: 50}),
|
|
627
|
+
(state, {attribute: 100}),
|
|
628
|
+
],
|
|
629
|
+
other_states=[
|
|
630
|
+
(state, {attribute: None}),
|
|
631
|
+
(state, {attribute: 0}),
|
|
632
|
+
],
|
|
633
|
+
),
|
|
634
|
+
*parametrize_trigger_states(
|
|
635
|
+
trigger=trigger,
|
|
636
|
+
trigger_options={
|
|
637
|
+
CONF_THRESHOLD_TYPE: ThresholdType.BELOW,
|
|
638
|
+
CONF_UPPER_LIMIT: 90,
|
|
639
|
+
},
|
|
640
|
+
target_states=[
|
|
641
|
+
(state, {attribute: 0}),
|
|
642
|
+
(state, {attribute: 50}),
|
|
643
|
+
],
|
|
644
|
+
other_states=[
|
|
645
|
+
(state, {attribute: None}),
|
|
646
|
+
(state, {attribute: 100}),
|
|
647
|
+
],
|
|
648
|
+
),
|
|
649
|
+
]
|
|
650
|
+
|
|
651
|
+
|
|
342
652
|
async def arm_trigger(
|
|
343
653
|
hass: HomeAssistant,
|
|
344
654
|
trigger: str,
|
|
345
|
-
trigger_options: dict | None,
|
|
655
|
+
trigger_options: dict[str, Any] | None,
|
|
346
656
|
trigger_target: dict,
|
|
347
657
|
) -> None:
|
|
348
658
|
"""Arm the specified trigger, call service test.automation when it triggers."""
|
|
@@ -371,10 +681,28 @@ async def arm_trigger(
|
|
|
371
681
|
)
|
|
372
682
|
|
|
373
683
|
|
|
684
|
+
async def create_target_condition(
|
|
685
|
+
hass: HomeAssistant,
|
|
686
|
+
*,
|
|
687
|
+
condition: str,
|
|
688
|
+
target: dict,
|
|
689
|
+
behavior: str,
|
|
690
|
+
) -> ConditionCheckerTypeOptional:
|
|
691
|
+
"""Create a target condition."""
|
|
692
|
+
return await async_condition_from_config(
|
|
693
|
+
hass,
|
|
694
|
+
{
|
|
695
|
+
CONF_CONDITION: condition,
|
|
696
|
+
CONF_TARGET: target,
|
|
697
|
+
CONF_OPTIONS: {"behavior": behavior},
|
|
698
|
+
},
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
|
|
374
702
|
def set_or_remove_state(
|
|
375
703
|
hass: HomeAssistant,
|
|
376
704
|
entity_id: str,
|
|
377
|
-
state:
|
|
705
|
+
state: TriggerStateDescription,
|
|
378
706
|
) -> None:
|
|
379
707
|
"""Set or remove the state of an entity."""
|
|
380
708
|
if state["state"] is None:
|
|
@@ -397,3 +725,37 @@ def other_states(state: StrEnum | Iterable[StrEnum]) -> list[str]:
|
|
|
397
725
|
enum_class = list(state)[0].__class__
|
|
398
726
|
|
|
399
727
|
return sorted({s.value for s in enum_class} - excluded_values)
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
async def assert_condition_gated_by_labs_flag(
|
|
731
|
+
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, condition: str
|
|
732
|
+
) -> None:
|
|
733
|
+
"""Helper to check that a condition is gated by the labs flag."""
|
|
734
|
+
|
|
735
|
+
# Local include to avoid importing the automation component unnecessarily
|
|
736
|
+
from homeassistant.components import automation # noqa: PLC0415
|
|
737
|
+
|
|
738
|
+
await async_setup_component(
|
|
739
|
+
hass,
|
|
740
|
+
automation.DOMAIN,
|
|
741
|
+
{
|
|
742
|
+
automation.DOMAIN: {
|
|
743
|
+
"trigger": {"platform": "event", "event_type": "test_event"},
|
|
744
|
+
"condition": {
|
|
745
|
+
CONF_CONDITION: condition,
|
|
746
|
+
CONF_TARGET: {ATTR_LABEL_ID: "test_label"},
|
|
747
|
+
CONF_OPTIONS: {"behavior": "any"},
|
|
748
|
+
},
|
|
749
|
+
"action": {
|
|
750
|
+
"service": "test.automation",
|
|
751
|
+
},
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
assert (
|
|
757
|
+
"Unnamed automation failed to setup conditions and has been disabled: "
|
|
758
|
+
f"Condition '{condition}' requires the experimental 'New triggers and "
|
|
759
|
+
"conditions' feature to be enabled in Home Assistant Labs settings "
|
|
760
|
+
"(feature flag: 'new_triggers_conditions')"
|
|
761
|
+
) in caplog.text
|
|
@@ -5,8 +5,8 @@ This file is originally from homeassistant/core and modified by pytest-homeassis
|
|
|
5
5
|
"""
|
|
6
6
|
from typing import TYPE_CHECKING, Final
|
|
7
7
|
MAJOR_VERSION: Final = 2026
|
|
8
|
-
MINOR_VERSION: Final =
|
|
9
|
-
PATCH_VERSION: Final = "
|
|
8
|
+
MINOR_VERSION: Final = 2
|
|
9
|
+
PATCH_VERSION: Final = "0b0"
|
|
10
10
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
|
11
11
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
|
12
12
|
CONF_API_VERSION: Final = "api_version"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-homeassistant-custom-component
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.309
|
|
4
4
|
Summary: Experimental package to automatically extract test plugins for Home Assistant custom components
|
|
5
5
|
Home-page: https://github.com/MatthewFlamm/pytest-homeassistant-custom-component
|
|
6
6
|
Author: Matthew Flamm
|
|
@@ -22,6 +22,7 @@ Requires-Dist: coverage==7.10.6
|
|
|
22
22
|
Requires-Dist: freezegun==1.5.2
|
|
23
23
|
Requires-Dist: license-expression==30.4.3
|
|
24
24
|
Requires-Dist: mock-open==1.4.0
|
|
25
|
+
Requires-Dist: prek==0.2.28
|
|
25
26
|
Requires-Dist: pydantic==2.12.2
|
|
26
27
|
Requires-Dist: pylint-per-file-ignores==1.4.0
|
|
27
28
|
Requires-Dist: pipdeptree==2.26.1
|
|
@@ -41,7 +42,7 @@ Requires-Dist: requests-mock==1.12.1
|
|
|
41
42
|
Requires-Dist: respx==0.22.0
|
|
42
43
|
Requires-Dist: syrupy==5.0.0
|
|
43
44
|
Requires-Dist: tqdm==4.67.1
|
|
44
|
-
Requires-Dist: homeassistant==2026.
|
|
45
|
+
Requires-Dist: homeassistant==2026.2.0b0
|
|
45
46
|
Requires-Dist: SQLAlchemy==2.0.41
|
|
46
47
|
Requires-Dist: paho-mqtt==2.1.0
|
|
47
48
|
Requires-Dist: numpy==2.3.2
|
|
@@ -59,7 +60,7 @@ Dynamic: summary
|
|
|
59
60
|
|
|
60
61
|
# pytest-homeassistant-custom-component
|
|
61
62
|
|
|
62
|
-

|
|
63
64
|
|
|
64
65
|
Package to automatically extract testing plugins from Home Assistant for custom component testing.
|
|
65
66
|
The goal is to provide the same functionality as the tests in home-assistant/core.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
pytest_homeassistant_custom_component/__init__.py,sha256=pUI8j-H-57ncCLnvZSDWZPCtJpvi3ACZqPtH5SbedZA,138
|
|
2
2
|
pytest_homeassistant_custom_component/asyncio_legacy.py,sha256=UdkV2mKqeS21QX9LSdBYsBRbm2h4JCVVZeesaOLKOAE,3886
|
|
3
|
-
pytest_homeassistant_custom_component/common.py,sha256=
|
|
4
|
-
pytest_homeassistant_custom_component/const.py,sha256=
|
|
3
|
+
pytest_homeassistant_custom_component/common.py,sha256=1Kwhjp96thdU6X-y8zN-GpRHerz_xmyz_JYd6Xtp3TI,65565
|
|
4
|
+
pytest_homeassistant_custom_component/const.py,sha256=ETxizOD9evHcYSi86bxudFLPIsATd21ykClJlqoVniQ,441
|
|
5
5
|
pytest_homeassistant_custom_component/ignore_uncaught_exceptions.py,sha256=zM7gPOjojoh_c9bk9ln9zfrVquYz8GroR1a-Hn3qkVA,1646
|
|
6
6
|
pytest_homeassistant_custom_component/patch_json.py,sha256=hNUeb1yxAr7ONfvX-o_WkI6zhQDCdKl7GglPjkVUiHo,1063
|
|
7
7
|
pytest_homeassistant_custom_component/patch_recorder.py,sha256=lW8N_3ZIKQ5lsVjRc-ROo7d0egUZcpjquWKqe7iEF94,819
|
|
@@ -9,7 +9,7 @@ pytest_homeassistant_custom_component/patch_time.py,sha256=jdnOAXDxUA0AKqvyeSrRC
|
|
|
9
9
|
pytest_homeassistant_custom_component/plugins.py,sha256=ZZlTVoPEJMqTiyJcVrxJaykxG4G6Ij_3_Jb-ANGj6Hw,70117
|
|
10
10
|
pytest_homeassistant_custom_component/syrupy.py,sha256=N_g_90dWqruzUogQi0rJsuN0XRbA6ffJen62r8P9cdo,15588
|
|
11
11
|
pytest_homeassistant_custom_component/typing.py,sha256=6Qr1lDzFwCCYZzvgXsnJDJcRXksojQPNn87NPc7YTeE,1739
|
|
12
|
-
pytest_homeassistant_custom_component/components/__init__.py,sha256=
|
|
12
|
+
pytest_homeassistant_custom_component/components/__init__.py,sha256=pzGuZ-P4gebfb2R8MxHlFJzRwHIoNlgit1psadQG8JU,25664
|
|
13
13
|
pytest_homeassistant_custom_component/components/diagnostics/__init__.py,sha256=O_ys8t0iHvRorFr4TrR9k3sa3Xh5qBb4HsylY775UFA,2431
|
|
14
14
|
pytest_homeassistant_custom_component/components/recorder/__init__.py,sha256=ugrLzvjSQFSmYRjy88ZZSiyA-NLgKlLkFp0OKguy6a4,225
|
|
15
15
|
pytest_homeassistant_custom_component/components/recorder/common.py,sha256=_o0TjbqHCJ67U2prFqySO0vxx_oIn2RsKxFxNPitTfQ,22097
|
|
@@ -19,10 +19,10 @@ pytest_homeassistant_custom_component/test_util/aiohttp.py,sha256=sJHmGf4Oig0SUM
|
|
|
19
19
|
pytest_homeassistant_custom_component/testing_config/__init__.py,sha256=SRp6h9HJi2I_vA6cPNkMiR0BTYib5XVmL03H-l3BPL0,158
|
|
20
20
|
pytest_homeassistant_custom_component/testing_config/custom_components/__init__.py,sha256=-l6KCBLhwEDkCztlY6S-j53CjmKY6-A_3eX5JVS02NY,173
|
|
21
21
|
pytest_homeassistant_custom_component/testing_config/custom_components/test_constant_deprecation/__init__.py,sha256=2vF_C-VP9tDjZMX7h6iJRAugtH2Bf3b4fE3i9j4vGeY,383
|
|
22
|
-
pytest_homeassistant_custom_component-0.13.
|
|
23
|
-
pytest_homeassistant_custom_component-0.13.
|
|
24
|
-
pytest_homeassistant_custom_component-0.13.
|
|
25
|
-
pytest_homeassistant_custom_component-0.13.
|
|
26
|
-
pytest_homeassistant_custom_component-0.13.
|
|
27
|
-
pytest_homeassistant_custom_component-0.13.
|
|
28
|
-
pytest_homeassistant_custom_component-0.13.
|
|
22
|
+
pytest_homeassistant_custom_component-0.13.309.dist-info/licenses/LICENSE,sha256=7h-vqUxyeQNXiQgRJ8350CSHOy55M07DZuv4KG70AS8,1070
|
|
23
|
+
pytest_homeassistant_custom_component-0.13.309.dist-info/licenses/LICENSE_HA_CORE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
24
|
+
pytest_homeassistant_custom_component-0.13.309.dist-info/METADATA,sha256=Xt8k-0dpn4epLXnQAwb5Kl3PnT128nnnrYqJAZs5lww,5768
|
|
25
|
+
pytest_homeassistant_custom_component-0.13.309.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
26
|
+
pytest_homeassistant_custom_component-0.13.309.dist-info/entry_points.txt,sha256=bOCTSuP8RSPg0QfwdfurUShvMGWg4MI2F8rxbWx-VtQ,73
|
|
27
|
+
pytest_homeassistant_custom_component-0.13.309.dist-info/top_level.txt,sha256=PR2cize2la22eOO7dQChJWK8dkJnuMmDC-fhafmdOWw,38
|
|
28
|
+
pytest_homeassistant_custom_component-0.13.309.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|