pytest-homeassistant-custom-component 0.13.332__tar.gz → 0.13.334__tar.gz

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.
Files changed (38) hide show
  1. {pytest_homeassistant_custom_component-0.13.332/src/pytest_homeassistant_custom_component.egg-info → pytest_homeassistant_custom_component-0.13.334}/PKG-INFO +9 -7
  2. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/README.md +1 -1
  3. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/common.py +65 -22
  4. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/common.py +84 -48
  5. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/recorder/common.py +1 -3
  6. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/const.py +2 -2
  7. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/patch_json.py +0 -2
  8. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/patch_recorder.py +0 -2
  9. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/patch_time.py +0 -2
  10. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/plugins.py +43 -21
  11. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/syrupy.py +5 -5
  12. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/typing.py +0 -2
  13. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334/src/pytest_homeassistant_custom_component.egg-info}/PKG-INFO +9 -7
  14. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/requires.txt +7 -5
  15. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/LICENSE +0 -0
  16. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/LICENSE_HA_CORE.md +0 -0
  17. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/setup.cfg +0 -0
  18. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/setup.py +0 -0
  19. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/__init__.py +0 -0
  20. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/asyncio_legacy.py +0 -0
  21. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/__init__.py +0 -0
  22. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/diagnostics/__init__.py +0 -0
  23. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/recorder/__init__.py +0 -0
  24. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/recorder/db_schema_0.py +0 -0
  25. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/ignore_uncaught_exceptions.py +0 -0
  26. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/test_util/__init__.py +0 -0
  27. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/test_util/aiohttp.py +0 -0
  28. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/testing_config/__init__.py +0 -0
  29. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/testing_config/custom_components/__init__.py +0 -0
  30. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/testing_config/custom_components/test_constant_deprecation/__init__.py +0 -0
  31. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/SOURCES.txt +0 -0
  32. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/dependency_links.txt +0 -0
  33. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/entry_points.txt +0 -0
  34. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/top_level.txt +0 -0
  35. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_common.py +0 -0
  36. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_config_flow.py +0 -0
  37. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_diagnostics.py +0 -0
  38. {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_sensor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-homeassistant-custom-component
3
- Version: 0.13.332
3
+ Version: 0.13.334
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
@@ -18,12 +18,12 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  License-File: LICENSE_HA_CORE.md
20
20
  Requires-Dist: sqlalchemy
21
- Requires-Dist: coverage==7.13.5
21
+ Requires-Dist: ast-serialize==0.3.0
22
+ Requires-Dist: coverage==7.14.0
22
23
  Requires-Dist: freezegun==1.5.5
23
24
  Requires-Dist: license-expression==30.4.3
24
25
  Requires-Dist: mock-open==1.4.0
25
- Requires-Dist: pydantic==2.13.2
26
- Requires-Dist: pylint-per-file-ignores==3.2.1
26
+ Requires-Dist: pydantic==2.13.4
27
27
  Requires-Dist: pipdeptree==2.26.1
28
28
  Requires-Dist: pytest-asyncio==1.3.0
29
29
  Requires-Dist: pytest-aiohttp==1.1.0
@@ -36,11 +36,13 @@ Requires-Dist: pytest-unordered==0.7.0
36
36
  Requires-Dist: pytest-picked==0.5.1
37
37
  Requires-Dist: pytest-xdist==3.8.0
38
38
  Requires-Dist: pytest==9.0.3
39
+ Requires-Dist: requests==2.34.2
39
40
  Requires-Dist: requests-mock==1.12.1
40
41
  Requires-Dist: respx==0.23.1
41
- Requires-Dist: syrupy==5.1.0
42
+ Requires-Dist: syrupy==5.2.0
42
43
  Requires-Dist: tqdm==4.67.1
43
- Requires-Dist: homeassistant==2026.5.3
44
+ Requires-Dist: unidiff==0.7.5
45
+ Requires-Dist: homeassistant==2026.6.0b0
44
46
  Requires-Dist: aiohasupervisor==0.4.3
45
47
  Requires-Dist: SQLAlchemy==2.0.49
46
48
  Requires-Dist: paho-mqtt==2.1.0
@@ -59,7 +61,7 @@ Dynamic: summary
59
61
 
60
62
  # pytest-homeassistant-custom-component
61
63
 
62
- ![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2026.5.3&labelColor=blue)
64
+ ![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2026.6.0b0&labelColor=blue)
63
65
 
64
66
  Package to automatically extract testing plugins from Home Assistant for custom component testing.
65
67
  The goal is to provide the same functionality as the tests in home-assistant/core.
@@ -1,6 +1,6 @@
1
1
  # pytest-homeassistant-custom-component
2
2
 
3
- ![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2026.5.3&labelColor=blue)
3
+ ![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2026.6.0b0&labelColor=blue)
4
4
 
5
5
  Package to automatically extract testing plugins from Home Assistant for custom component testing.
6
6
  The goal is to provide the same functionality as the tests in home-assistant/core.
@@ -4,8 +4,6 @@ Test the helper method for writing tests.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  import asyncio
10
8
  from collections.abc import (
11
9
  AsyncGenerator,
@@ -30,12 +28,13 @@ import pathlib
30
28
  import time
31
29
  import traceback
32
30
  from types import FrameType, ModuleType
33
- from typing import Any, Literal, NoReturn
31
+ from typing import TYPE_CHECKING, Any, Literal, NoReturn
34
32
  from unittest.mock import AsyncMock, Mock, patch
35
33
 
36
34
  from aiohttp.test_utils import unused_port as get_test_instance_port
37
35
  from annotatedyaml import load_yaml_dict, loader as yaml_loader
38
36
  import attr
37
+ from paho.mqtt.client import MQTTMessage
39
38
  import pytest
40
39
  from syrupy.assertion import SnapshotAssertion
41
40
  import voluptuous as vol
@@ -106,6 +105,7 @@ from homeassistant.helpers.entity_platform import (
106
105
  )
107
106
  from homeassistant.helpers.json import JSONEncoder, _orjson_default_encoder, json_dumps
108
107
  from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
108
+ from homeassistant.setup import async_setup_component
109
109
  from homeassistant.util import dt as dt_util, ulid as ulid_util, uuid as uuid_util
110
110
  from homeassistant.util.async_ import (
111
111
  _SHUTDOWN_RUN_CALLBACK_THREADSAFE,
@@ -128,6 +128,9 @@ from .testing_config.custom_components.test_constant_deprecation import (
128
128
  import_deprecated_constant,
129
129
  )
130
130
 
131
+ if TYPE_CHECKING:
132
+ import paho.mqtt.client as mqtt
133
+
131
134
  __all__ = [
132
135
  "async_get_device_automation_capabilities",
133
136
  "get_test_instance_port",
@@ -327,7 +330,8 @@ async def async_test_home_assistant(
327
330
  StoreWithoutWriteLoad,
328
331
  ),
329
332
  patch(
330
- "homeassistant.helpers.storage.Store", # Floor & label registry are different
333
+ # Floor & label registry are different
334
+ "homeassistant.helpers.storage.Store",
331
335
  StoreWithoutWriteLoad,
332
336
  ),
333
337
  patch(
@@ -458,13 +462,9 @@ def async_fire_mqtt_message(
458
462
  payload: bytes | str,
459
463
  qos: int = 0,
460
464
  retain: bool = False,
465
+ properties: mqtt.Properties | None = None,
461
466
  ) -> None:
462
467
  """Fire the MQTT message."""
463
- # Local import to avoid processing MQTT modules when running a testcase
464
- # which does not use MQTT.
465
-
466
- from paho.mqtt.client import MQTTMessage # noqa: PLC0415
467
-
468
468
  from homeassistant.components.mqtt import MqttData # noqa: PLC0415
469
469
 
470
470
  if isinstance(payload, str):
@@ -475,6 +475,7 @@ def async_fire_mqtt_message(
475
475
  msg.qos = qos
476
476
  msg.retain = retain
477
477
  msg.timestamp = time.monotonic()
478
+ msg.properties = properties
478
479
 
479
480
  mqtt_data: MqttData = hass.data["mqtt"]
480
481
  assert mqtt_data.client
@@ -773,7 +774,6 @@ def mock_device_registry(
773
774
  registry.deleted_devices = dr.DeviceRegistryItems()
774
775
 
775
776
  hass.data[dr.DATA_REGISTRY] = registry
776
- dr.async_get.cache_clear()
777
777
  return registry
778
778
 
779
779
 
@@ -1184,8 +1184,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
1184
1184
  async def start_reconfigure_flow(
1185
1185
  self,
1186
1186
  hass: HomeAssistant,
1187
- *,
1188
- show_advanced_options: bool = False,
1189
1187
  ) -> ConfigFlowResult:
1190
1188
  """Start a reconfiguration flow."""
1191
1189
  if self.entry_id not in hass.config_entries._entries:
@@ -1197,7 +1195,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
1197
1195
  context={
1198
1196
  "source": config_entries.SOURCE_RECONFIGURE,
1199
1197
  "entry_id": self.entry_id,
1200
- "show_advanced_options": show_advanced_options,
1201
1198
  },
1202
1199
  )
1203
1200
 
@@ -1205,8 +1202,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
1205
1202
  self,
1206
1203
  hass: HomeAssistant,
1207
1204
  subentry_id: str,
1208
- *,
1209
- show_advanced_options: bool = False,
1210
1205
  ) -> ConfigFlowResult:
1211
1206
  """Start a subentry reconfiguration flow."""
1212
1207
  if self.entry_id not in hass.config_entries._entries:
@@ -1220,7 +1215,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
1220
1215
  context={
1221
1216
  "source": config_entries.SOURCE_RECONFIGURE,
1222
1217
  "subentry_id": subentry_id,
1223
- "show_advanced_options": show_advanced_options,
1224
1218
  },
1225
1219
  )
1226
1220
 
@@ -1262,7 +1256,7 @@ def patch_yaml_files(files_dict, endswith=True):
1262
1256
  if fname in files_dict:
1263
1257
  _LOGGER.debug("patch_yaml_files match %s", fname)
1264
1258
  res = StringIO(files_dict[fname])
1265
- setattr(res, "name", fname)
1259
+ res.name = fname
1266
1260
  return res
1267
1261
 
1268
1262
  # Match using endswith
@@ -1270,7 +1264,7 @@ def patch_yaml_files(files_dict, endswith=True):
1270
1264
  if fname.endswith(ends):
1271
1265
  _LOGGER.debug("patch_yaml_files end match %s: %s", ends, fname)
1272
1266
  res = StringIO(files_dict[ends])
1273
- setattr(res, "name", fname)
1267
+ res.name = fname
1274
1268
  return res
1275
1269
 
1276
1270
  # Fallback for hass.components (i.e. services.yaml)
@@ -1458,12 +1452,12 @@ class MockEntity(entity.Entity):
1458
1452
 
1459
1453
  @property
1460
1454
  def entity_registry_enabled_default(self) -> bool:
1461
- """Return if the entity should be enabled when first added to the entity registry."""
1455
+ """Return if the entity should be enabled in the registry when first added."""
1462
1456
  return self._handle("entity_registry_enabled_default")
1463
1457
 
1464
1458
  @property
1465
1459
  def entity_registry_visible_default(self) -> bool:
1466
- """Return if the entity should be visible when first added to the entity registry."""
1460
+ """Return if the entity should be visible in the registry when first added."""
1467
1461
  return self._handle("entity_registry_visible_default")
1468
1462
 
1469
1463
  @property
@@ -1639,7 +1633,8 @@ def mock_integration(
1639
1633
 
1640
1634
  def mock_import_platform(platform_name: str) -> NoReturn:
1641
1635
  raise ImportError(
1642
- f"Mocked unable to import platform '{integration.pkg_path}.{platform_name}'",
1636
+ "Mocked unable to import platform"
1637
+ f" '{integration.pkg_path}.{platform_name}'",
1643
1638
  name=f"{integration.pkg_path}.{platform_name}",
1644
1639
  )
1645
1640
 
@@ -1864,6 +1859,8 @@ def import_and_test_deprecated_alias(
1864
1859
  alias_name: str,
1865
1860
  replacement: Any,
1866
1861
  breaks_in_ha_version: str,
1862
+ *,
1863
+ replacement_name: str | None = None,
1867
1864
  ) -> None:
1868
1865
  """Import and test deprecated alias replaced by a value.
1869
1866
 
@@ -1873,7 +1870,9 @@ def import_and_test_deprecated_alias(
1873
1870
  - Assert the deprecated alias is included in the modules.__dir__()
1874
1871
  - Assert the deprecated alias is included in the modules.__all__()
1875
1872
  """
1876
- replacement_name = f"{replacement.__module__}.{replacement.__name__}"
1873
+ replacement_name = (
1874
+ replacement_name or f"{replacement.__module__}.{replacement.__name__}"
1875
+ )
1877
1876
  value = import_deprecated_constant(module, alias_name)
1878
1877
  assert value == replacement
1879
1878
  assert (
@@ -2033,3 +2032,47 @@ def get_sensor_display_state(
2033
2032
  numerical_value = float(value)
2034
2033
  value = f"{numerical_value:z.{precision}f}"
2035
2034
  return value
2035
+
2036
+
2037
+ async def assert_platform_setup_creates_issue(
2038
+ hass: HomeAssistant,
2039
+ platform_domain: str,
2040
+ integration_domain: str,
2041
+ issue_registry: ir.IssueRegistry,
2042
+ caplog: pytest.LogCaptureFixture,
2043
+ ) -> None:
2044
+ """Assert that setting up a platform creates an issue."""
2045
+ caplog.clear()
2046
+ with assert_setup_component(1, platform_domain):
2047
+ assert await async_setup_component(
2048
+ hass,
2049
+ platform_domain,
2050
+ {platform_domain: {"platform": integration_domain}},
2051
+ )
2052
+ await hass.async_block_till_done()
2053
+ await hass.async_start()
2054
+ await hass.async_block_till_done()
2055
+
2056
+ assert len(hass.states.async_all(platform_domain)) == 0
2057
+ assert (
2058
+ f"Configuring the {integration_domain} integration under the {platform_domain} platform key is not"
2059
+ f" supported, it must be configured under its own {integration_domain} key instead"
2060
+ in caplog.text
2061
+ )
2062
+
2063
+ issue = issue_registry.async_get_issue(
2064
+ "homeassistant",
2065
+ f"platform_integration_no_support_{platform_domain}_{integration_domain}",
2066
+ )
2067
+
2068
+ assert issue
2069
+ assert issue.issue_domain == integration_domain
2070
+ assert issue.learn_more_url is not None
2071
+ assert issue.translation_key == "platform_config_not_supported"
2072
+ assert issue.severity == ir.IssueSeverity.ERROR
2073
+ assert issue.translation_placeholders == {
2074
+ "platform_domain": platform_domain,
2075
+ "integration_domain": integration_domain,
2076
+ "platform_key": f"platform: {integration_domain}",
2077
+ "yaml_example": f"```yaml\n{platform_domain}:\n - platform: {integration_domain}\n```",
2078
+ }
@@ -223,7 +223,10 @@ class BasicTriggerStateDescription(TypedDict):
223
223
 
224
224
 
225
225
  class TriggerStateDescription(BasicTriggerStateDescription):
226
- """Test state and expected service call count for both included and excluded entities."""
226
+ """Test state and expected service call count.
227
+
228
+ Covers both included and excluded entities.
229
+ """
227
230
 
228
231
  excluded_state: StateDescription # State for entities not meant to be targeted
229
232
  # State for the *other* targeted entities (the ones not under direct test).
@@ -241,7 +244,9 @@ class ConditionStateDescription(TypedDict):
241
244
  excluded_state: StateDescription # State for entities not meant to be targeted
242
245
 
243
246
  condition_true: bool # If the condition is expected to evaluate to true
244
- condition_true_first_entity: bool # If the condition is expected to evaluate to true for the first targeted entity
247
+ # If the condition is expected to evaluate to true
248
+ # for the first targeted entity
249
+ condition_true_first_entity: bool
245
250
 
246
251
 
247
252
  def _parametrize_condition_states(
@@ -840,7 +845,7 @@ def parametrize_numerical_attribute_changed_trigger_states(
840
845
  attribute_value_scale: float = 1.0,
841
846
  attribute_required: bool = False,
842
847
  ) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
843
- """Parametrize states and expected service call counts for numerical-changed triggers.
848
+ """Parametrize states for numerical-changed triggers.
844
849
 
845
850
  Generates state sequences for a trigger that fires whenever an attribute
846
851
  crosses or matches a "changed" threshold (modes "any" / "above" / "below").
@@ -986,7 +991,7 @@ def parametrize_numerical_attribute_crossed_threshold_trigger_states(
986
991
  attribute_value_scale: float = 1.0,
987
992
  attribute_required: bool = False,
988
993
  ) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
989
- """Parametrize states and expected service call counts for numerical crossed-threshold triggers.
994
+ """Parametrize states for numerical crossed-threshold triggers.
990
995
 
991
996
  Generates state sequences for a trigger that fires when an attribute
992
997
  crosses a threshold boundary. The trigger is exercised across four
@@ -1061,8 +1066,8 @@ def parametrize_numerical_attribute_crossed_threshold_trigger_states(
1061
1066
  threshold_unit,
1062
1067
  ),
1063
1068
  target_states=[
1064
- (state, {attribute: 50 * s} | unit_attributes),
1065
- (state, {attribute: 60 * s} | unit_attributes),
1069
+ (state, {attribute: 10 * s} | unit_attributes),
1070
+ (state, {attribute: 90 * s} | unit_attributes),
1066
1071
  ],
1067
1072
  other_states=[
1068
1073
  other_invalid_attr,
@@ -1091,8 +1096,8 @@ def parametrize_numerical_attribute_crossed_threshold_trigger_states(
1091
1096
  ],
1092
1097
  other_states=[
1093
1098
  other_invalid_attr,
1094
- (state, {attribute: 50 * s} | unit_attributes),
1095
- (state, {attribute: 60 * s} | unit_attributes),
1099
+ (state, {attribute: 10 * s} | unit_attributes),
1100
+ (state, {attribute: 90 * s} | unit_attributes),
1096
1101
  ],
1097
1102
  extra_excluded_states=extra_excluded_states,
1098
1103
  required_filter_attributes=required_filter_attributes,
@@ -1154,11 +1159,11 @@ def parametrize_numerical_state_value_changed_trigger_states(
1154
1159
  trigger_options: dict[str, Any] | None = None,
1155
1160
  unit_attributes: dict | None = None,
1156
1161
  ) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
1157
- """Parametrize states and expected service call counts for numerical state-value changed triggers.
1162
+ """Parametrize states for numerical state-value changed triggers.
1158
1163
 
1159
- Unlike parametrize_numerical_attribute_changed_trigger_states, this is for
1160
- entities where the tracked numerical value is in state.state (e.g. sensor
1161
- entities), not in an attribute.
1164
+ Unlike parametrize_numerical_attribute_changed_trigger_states,
1165
+ this is for entities where the tracked numerical value is in
1166
+ state.state (e.g. sensor entities), not in an attribute.
1162
1167
  """
1163
1168
  from homeassistant.const import ATTR_DEVICE_CLASS # noqa: PLC0415
1164
1169
 
@@ -1235,7 +1240,7 @@ def parametrize_numerical_state_value_crossed_threshold_trigger_states(
1235
1240
  trigger_options: dict[str, Any] | None = None,
1236
1241
  unit_attributes: dict | None = None,
1237
1242
  ) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
1238
- """Parametrize states and expected service call counts for numerical state-value crossed threshold triggers.
1243
+ """Parametrize states for numerical state-value crossed threshold triggers.
1239
1244
 
1240
1245
  Unlike parametrize_numerical_attribute_crossed_threshold_trigger_states,
1241
1246
  this is for entities where the tracked numerical value is in state.state
@@ -1261,7 +1266,7 @@ def parametrize_numerical_state_value_crossed_threshold_trigger_states(
1261
1266
  },
1262
1267
  threshold_unit,
1263
1268
  ),
1264
- target_states=[("50", unit_attributes), ("60", unit_attributes)],
1269
+ target_states=[("10", unit_attributes), ("90", unit_attributes)],
1265
1270
  other_states=[
1266
1271
  ("none", unit_attributes),
1267
1272
  ("0", unit_attributes),
@@ -1286,8 +1291,8 @@ def parametrize_numerical_state_value_crossed_threshold_trigger_states(
1286
1291
  target_states=[("0", unit_attributes), ("100", unit_attributes)],
1287
1292
  other_states=[
1288
1293
  ("none", unit_attributes),
1289
- ("50", unit_attributes),
1290
- ("60", unit_attributes),
1294
+ ("10", unit_attributes),
1295
+ ("90", unit_attributes),
1291
1296
  ],
1292
1297
  required_filter_attributes=required_filter_attributes,
1293
1298
  trigger_from_none=False,
@@ -1336,7 +1341,7 @@ async def arm_trigger(
1336
1341
  trigger_target: dict,
1337
1342
  calls: list[str],
1338
1343
  ) -> None:
1339
- """Arm the specified trigger and record fired entity_ids in calls when it triggers."""
1344
+ """Arm the trigger and record fired entity_ids in calls."""
1340
1345
  options = {CONF_OPTIONS: {**trigger_options}} if trigger_options is not None else {}
1341
1346
 
1342
1347
  trigger_config = {
@@ -1485,7 +1490,7 @@ async def _validate_condition_options(
1485
1490
  *,
1486
1491
  valid: bool,
1487
1492
  ) -> None:
1488
- """Assert that a condition accepts or rejects the given options during validation."""
1493
+ """Assert that a condition accepts or rejects the given options."""
1489
1494
  config: dict[str, Any] = {
1490
1495
  CONF_CONDITION: condition,
1491
1496
  CONF_TARGET: {ATTR_LABEL_ID: "test_label"},
@@ -1631,7 +1636,7 @@ async def assert_trigger_options_supported(
1631
1636
  return {**(base_options or {}), **extra}
1632
1637
 
1633
1638
  # Behavior
1634
- for behavior in ("any", "first", "last"):
1639
+ for behavior in ("each", "first", "all"):
1635
1640
  await _validate_trigger_options(
1636
1641
  hass, trigger, _merge({"behavior": behavior}), valid=supports_behavior
1637
1642
  )
@@ -1749,7 +1754,7 @@ async def assert_condition_behavior_all(
1749
1754
  assert cond.async_check() == state["condition_true"]
1750
1755
 
1751
1756
 
1752
- async def assert_trigger_behavior_any(
1757
+ async def assert_trigger_behavior_each(
1753
1758
  hass: HomeAssistant,
1754
1759
  *,
1755
1760
  target_entities: dict[str, list[str]],
@@ -1760,7 +1765,7 @@ async def assert_trigger_behavior_any(
1760
1765
  trigger_options: dict[str, Any],
1761
1766
  states: list[TriggerStateDescription],
1762
1767
  ) -> None:
1763
- """Test trigger fires in mode any."""
1768
+ """Test trigger fires in mode each."""
1764
1769
  calls: list[str] = []
1765
1770
  other_entity_ids = set(target_entities["included_entities"]) - {entity_id}
1766
1771
  excluded_entity_ids = set(target_entities["excluded_entities"]) - {entity_id}
@@ -1854,7 +1859,7 @@ async def assert_trigger_behavior_first(
1854
1859
  assert len(calls) == 0
1855
1860
 
1856
1861
 
1857
- async def assert_trigger_behavior_last(
1862
+ async def assert_trigger_behavior_all(
1858
1863
  hass: HomeAssistant,
1859
1864
  *,
1860
1865
  target_entities: dict[str, list[str]],
@@ -1865,7 +1870,7 @@ async def assert_trigger_behavior_last(
1865
1870
  trigger_options: dict[str, Any],
1866
1871
  states: list[TriggerStateDescription],
1867
1872
  ) -> None:
1868
- """Test trigger fires in mode last."""
1873
+ """Test trigger fires in mode all."""
1869
1874
  calls: list[str] = []
1870
1875
  other_entity_ids = set(target_entities["included_entities"]) - {entity_id}
1871
1876
  excluded_entity_ids = set(target_entities["excluded_entities"]) - {entity_id}
@@ -1880,7 +1885,7 @@ async def assert_trigger_behavior_last(
1880
1885
  await arm_trigger(
1881
1886
  hass,
1882
1887
  trigger,
1883
- {"behavior": "last"} | trigger_options,
1888
+ {"behavior": "all"} | trigger_options,
1884
1889
  trigger_target_config,
1885
1890
  calls,
1886
1891
  )
@@ -1915,10 +1920,11 @@ def parametrize_numerical_condition_above_below_any(
1915
1920
  threshold_unit: str | None | UndefinedType = UNDEFINED,
1916
1921
  unit_attributes: dict | None = None,
1917
1922
  ) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
1918
- """Parametrize above/below/between threshold cases for state-value numerical conditions under behavior=any.
1923
+ """Parametrize threshold cases for state-value numerical conditions.
1919
1924
 
1920
- Generates state sequences for a condition that reads its tracked value
1921
- directly from `state.state` (e.g. a sensor with a temperature device
1925
+ Uses behavior=any. Generates state sequences for a condition
1926
+ that reads its tracked value directly from `state.state`
1927
+ (e.g. a sensor with a temperature device
1922
1928
  class). The condition is exercised across three threshold types in turn
1923
1929
  — "above", "below", "between" — and for each, the helper invokes
1924
1930
  `parametrize_condition_states_any` with target/other states populated
@@ -2014,14 +2020,14 @@ def parametrize_numerical_condition_above_below_any(
2014
2020
  threshold_unit,
2015
2021
  ),
2016
2022
  target_states=[
2017
- ("21", unit_attributes),
2023
+ ("20", unit_attributes),
2018
2024
  ("50", unit_attributes),
2019
- ("79", unit_attributes),
2025
+ ("80", unit_attributes),
2020
2026
  ],
2021
2027
  other_states=[
2022
2028
  ("0", unit_attributes),
2023
- ("20", unit_attributes),
2024
- ("80", unit_attributes),
2029
+ ("19", unit_attributes),
2030
+ ("81", unit_attributes),
2025
2031
  ("100", unit_attributes),
2026
2032
  ],
2027
2033
  required_filter_attributes=required_filter_attributes,
@@ -2037,7 +2043,9 @@ def parametrize_numerical_condition_above_below_all(
2037
2043
  threshold_unit: str | None | UndefinedType = UNDEFINED,
2038
2044
  unit_attributes: dict | None = None,
2039
2045
  ) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
2040
- """Parametrize above/below/between threshold cases for state-value numerical conditions under behavior=all.
2046
+ """Parametrize threshold cases for state-value numerical conditions.
2047
+
2048
+ Uses behavior=all.
2041
2049
 
2042
2050
  See `parametrize_numerical_condition_above_below_any` for the structure
2043
2051
  of the generated test cases; the only difference is that this helper
@@ -2130,14 +2138,14 @@ def parametrize_numerical_condition_above_below_all(
2130
2138
  threshold_unit,
2131
2139
  ),
2132
2140
  target_states=[
2133
- ("21", unit_attributes),
2141
+ ("20", unit_attributes),
2134
2142
  ("50", unit_attributes),
2135
- ("79", unit_attributes),
2143
+ ("80", unit_attributes),
2136
2144
  ],
2137
2145
  other_states=[
2138
2146
  ("0", unit_attributes),
2139
- ("20", unit_attributes),
2140
- ("80", unit_attributes),
2147
+ ("19", unit_attributes),
2148
+ ("81", unit_attributes),
2141
2149
  ("100", unit_attributes),
2142
2150
  ],
2143
2151
  required_filter_attributes=required_filter_attributes,
@@ -2157,10 +2165,11 @@ def parametrize_numerical_attribute_condition_above_below_any(
2157
2165
  attribute_required: bool = False,
2158
2166
  attribute_value_scale: float = 1.0,
2159
2167
  ) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
2160
- """Parametrize above/below/between threshold cases for attribute-based numerical conditions under behavior=any.
2168
+ """Parametrize threshold cases for attribute-based numerical conditions.
2161
2169
 
2162
- Generates state sequences for a condition that reads its tracked value
2163
- from a state attribute (e.g. `climate.target_humidity`). The condition
2170
+ Uses behavior=any. Generates state sequences for a condition
2171
+ that reads its tracked value from a state attribute
2172
+ (e.g. `climate.target_humidity`). The condition
2164
2173
  is exercised across three threshold types in turn — "above", "below",
2165
2174
  "between" — and for each, the helper invokes
2166
2175
  `parametrize_condition_states_any` with target/other states populated
@@ -2276,14 +2285,14 @@ def parametrize_numerical_attribute_condition_above_below_any(
2276
2285
  threshold_unit,
2277
2286
  ),
2278
2287
  target_states=[
2279
- (state, {attribute: 21 * s} | unit_attributes),
2288
+ (state, {attribute: 20 * s} | unit_attributes),
2280
2289
  (state, {attribute: 50 * s} | unit_attributes),
2281
- (state, {attribute: 79 * s} | unit_attributes),
2290
+ (state, {attribute: 80 * s} | unit_attributes),
2282
2291
  ],
2283
2292
  other_states=[
2284
2293
  (state, {attribute: 0 * s} | unit_attributes),
2285
- (state, {attribute: 20 * s} | unit_attributes),
2286
- (state, {attribute: 80 * s} | unit_attributes),
2294
+ (state, {attribute: 19 * s} | unit_attributes),
2295
+ (state, {attribute: 81 * s} | unit_attributes),
2287
2296
  (state, {attribute: 100 * s} | unit_attributes),
2288
2297
  ],
2289
2298
  extra_excluded_states=extra_excluded_states,
@@ -2304,7 +2313,9 @@ def parametrize_numerical_attribute_condition_above_below_all(
2304
2313
  attribute_required: bool = False,
2305
2314
  attribute_value_scale: float = 1.0,
2306
2315
  ) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
2307
- """Parametrize above/below/between threshold cases for attribute-based numerical conditions under behavior=all.
2316
+ """Parametrize threshold cases for attribute-based numerical conditions.
2317
+
2318
+ Uses behavior=all.
2308
2319
 
2309
2320
  See `parametrize_numerical_attribute_condition_above_below_any` for the
2310
2321
  structure of the generated test cases; the only difference is that this
@@ -2421,14 +2432,14 @@ def parametrize_numerical_attribute_condition_above_below_all(
2421
2432
  threshold_unit,
2422
2433
  ),
2423
2434
  target_states=[
2424
- (state, {attribute: 21 * s} | unit_attributes),
2435
+ (state, {attribute: 20 * s} | unit_attributes),
2425
2436
  (state, {attribute: 50 * s} | unit_attributes),
2426
- (state, {attribute: 79 * s} | unit_attributes),
2437
+ (state, {attribute: 80 * s} | unit_attributes),
2427
2438
  ],
2428
2439
  other_states=[
2429
2440
  (state, {attribute: 0 * s} | unit_attributes),
2430
- (state, {attribute: 20 * s} | unit_attributes),
2431
- (state, {attribute: 80 * s} | unit_attributes),
2441
+ (state, {attribute: 19 * s} | unit_attributes),
2442
+ (state, {attribute: 81 * s} | unit_attributes),
2432
2443
  (state, {attribute: 100 * s} | unit_attributes),
2433
2444
  ],
2434
2445
  extra_excluded_states=extra_excluded_states,
@@ -2595,3 +2606,28 @@ async def assert_numerical_condition_unit_conversion(
2595
2606
  for state in fail_states:
2596
2607
  set_or_remove_state(hass, entity_id, state)
2597
2608
  assert cond.async_check() is False
2609
+
2610
+
2611
+ async def assert_availability_follows_source_entity(
2612
+ hass: HomeAssistant,
2613
+ entity_id: str,
2614
+ source_entity_id: str,
2615
+ ) -> None:
2616
+ """Check that entity becomes unavailable when source entity is unavailable."""
2617
+ state = hass.states.get(entity_id)
2618
+ assert state is not None
2619
+ assert state.state != STATE_UNAVAILABLE
2620
+
2621
+ hass.states.async_set(source_entity_id, STATE_UNAVAILABLE)
2622
+ await hass.async_block_till_done()
2623
+
2624
+ state = hass.states.get(entity_id)
2625
+ assert state is not None
2626
+ assert state.state == STATE_UNAVAILABLE
2627
+
2628
+ hass.states.async_set(source_entity_id, STATE_UNKNOWN)
2629
+ await hass.async_block_till_done()
2630
+
2631
+ state = hass.states.get(entity_id)
2632
+ assert state is not None
2633
+ assert state.state != STATE_UNAVAILABLE
@@ -4,8 +4,6 @@ Common test utils for working with recorder.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  import asyncio
10
8
  from collections.abc import Iterable, Iterator
11
9
  from contextlib import contextmanager
@@ -261,7 +259,7 @@ def assert_events_equal_without_context(event: Event, other: Event) -> None:
261
259
  """Assert that two events are equal, ignoring context."""
262
260
  assert event.data == other.data
263
261
  assert event.event_type == other.event_type
264
- assert event.origin == other.origin
262
+ assert event.origin is other.origin
265
263
  assert event.time_fired == other.time_fired
266
264
 
267
265
 
@@ -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 = 5
9
- PATCH_VERSION: Final = "3"
8
+ MINOR_VERSION: Final = 6
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"
@@ -4,8 +4,6 @@ Patch JSON related functions.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  import functools
10
8
  from typing import Any
11
9
  from unittest import mock
@@ -4,8 +4,6 @@ Patch recorder related functions.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  from contextlib import contextmanager
10
8
  import sys
11
9
 
@@ -4,8 +4,6 @@ Patch time related functions.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  import datetime
10
8
  import time
11
9
 
@@ -4,8 +4,6 @@ Set up some common test helper things.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  import asyncio
10
8
  from collections.abc import AsyncGenerator, Callable, Coroutine, Generator
11
9
  from contextlib import AsyncExitStack, asynccontextmanager, contextmanager
@@ -66,14 +64,14 @@ from homeassistant.auth.models import Credentials
66
64
  from homeassistant.auth.providers import homeassistant
67
65
  from homeassistant.components.device_tracker.legacy import Device
68
66
 
69
- # pylint: disable-next=hass-component-root-import
67
+ # pylint: disable-next=home-assistant-component-root-import
70
68
  from homeassistant.components.websocket_api.auth import (
71
69
  TYPE_AUTH,
72
70
  TYPE_AUTH_OK,
73
71
  TYPE_AUTH_REQUIRED,
74
72
  )
75
73
 
76
- # pylint: disable-next=hass-component-root-import
74
+ # pylint: disable-next=home-assistant-component-root-import
77
75
  from homeassistant.components.websocket_api.http import URL
78
76
  from homeassistant.config import YAML_CONFIG_FILE
79
77
  from homeassistant.config_entries import (
@@ -210,7 +208,9 @@ def pytest_runtest_setup() -> None:
210
208
  of times it was raised.
211
209
 
212
210
  freezegun:
213
- - Modified to include https://github.com/spulec/freezegun/pull/424 and improve class str.
211
+ - Modified to include
212
+ https://github.com/spulec/freezegun/pull/424
213
+ and improve class str.
214
214
  """
215
215
  pytest_socket.socket_allow_hosts(["127.0.0.1"])
216
216
  pytest_socket.disable_socket(allow_unix_socket=True)
@@ -235,9 +235,9 @@ def pytest_runtest_setup() -> None:
235
235
  _validate_host(host)
236
236
  return (host, [], [host])
237
237
 
238
- setattr(socket, "getaddrinfo", getaddrinfo_patched)
239
- setattr(socket, "gethostbyname", gethostbyname_patched)
240
- setattr(socket, "gethostbyname_ex", gethostbyname_ex_patched)
238
+ socket.getaddrinfo = getaddrinfo_patched
239
+ socket.gethostbyname = gethostbyname_patched
240
+ socket.gethostbyname_ex = gethostbyname_ex_patched
241
241
 
242
242
  pytest_socket.SocketBlockedError = HASocketBlockedError
243
243
 
@@ -457,7 +457,8 @@ def verify_cleanup(
457
457
  try:
458
458
  # Verify respx.mock has been cleaned up
459
459
  assert not respx.mock.routes, (
460
- "respx.mock routes not cleaned up, maybe the test needs to be decorated with @respx.mock"
460
+ "respx.mock routes not cleaned up, maybe the test"
461
+ " needs to be decorated with @respx.mock"
461
462
  )
462
463
  finally:
463
464
  # Clear mock routes not break subsequent tests
@@ -565,7 +566,9 @@ def aiohttp_client_cls() -> type[CoalescingClient]:
565
566
 
566
567
  @pytest.fixture
567
568
  def aiohttp_client() -> Generator[ClientSessionGenerator]:
568
- """Override the default aiohttp_client since 3.x does not support aiohttp_client_cls.
569
+ """Override the default aiohttp_client since 3.x does not support it.
570
+
571
+ The aiohttp_client_cls is not supported in 3.x.
569
572
 
570
573
  Remove this when upgrading to 4.x as aiohttp_client_cls
571
574
  will do the same thing
@@ -697,7 +700,8 @@ async def hass(
697
700
 
698
701
  yield hass
699
702
 
700
- # Config entries are not normally unloaded on HA shutdown. They are unloaded here
703
+ # Config entries are not normally unloaded on HA shutdown.
704
+ # They are unloaded here
701
705
  # to ensure that they could, and to help track lingering tasks and timers.
702
706
  loaded_entries = [
703
707
  entry
@@ -1017,7 +1021,11 @@ def hass_ws_client(
1017
1021
  def fail_on_log_exception(
1018
1022
  request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch
1019
1023
  ) -> None:
1020
- """Fixture to fail if a callback wrapped by catch_log_exception or coroutine wrapped by async_create_catching_coro throws."""
1024
+ """Fixture to fail if a callback or coroutine throws.
1025
+
1026
+ Catches callbacks wrapped by catch_log_exception or coroutines
1027
+ wrapped by async_create_catching_coro.
1028
+ """
1021
1029
  if "no_fail_on_log_exception" in request.keywords:
1022
1030
  return
1023
1031
 
@@ -1063,23 +1071,23 @@ def mqtt_client_mock(hass: HomeAssistant) -> Generator[MqttMockPahoClient]:
1063
1071
  self.mid = mid
1064
1072
  self.rc = 0
1065
1073
 
1066
- with patch(
1067
- "homeassistant.components.mqtt.async_client.AsyncMQTTClient"
1068
- ) as mock_client:
1074
+ with patch("homeassistant.components.mqtt.client.AsyncMQTTClient") as mock_client:
1069
1075
  # The below use a call_soon for the on_publish/on_subscribe/on_unsubscribe
1070
1076
  # callbacks to simulate the behavior of the real MQTT client which will
1071
1077
  # not be synchronous.
1072
1078
 
1073
1079
  @ha.callback
1074
- def _async_fire_mqtt_message(topic, payload, qos, retain):
1075
- async_fire_mqtt_message(hass, topic, payload or b"", qos, retain)
1080
+ def _async_fire_mqtt_message(topic, payload, qos, retain, properties=None):
1081
+ async_fire_mqtt_message(
1082
+ hass, topic, payload or b"", qos, retain, properties=properties
1083
+ )
1076
1084
  mid = get_mid()
1077
1085
  hass.loop.call_soon(
1078
1086
  mock_client.on_publish, Mock(), 0, mid, MockMqttReasonCode(), None
1079
1087
  )
1080
1088
  return FakeInfo(mid)
1081
1089
 
1082
- def _subscribe(topic, qos=0):
1090
+ def _subscribe(topic_or_list, qos=0, **kwargs):
1083
1091
  mid = get_mid()
1084
1092
  hass.loop.call_soon(
1085
1093
  mock_client.on_subscribe, Mock(), 0, mid, [MockMqttReasonCode()], None
@@ -1146,7 +1154,10 @@ async def _mqtt_mock_entry(
1146
1154
  from homeassistant.components import mqtt # noqa: PLC0415
1147
1155
 
1148
1156
  if mqtt_config_entry_data is None:
1149
- mqtt_config_entry_data = {mqtt.CONF_BROKER: "mock-broker"}
1157
+ mqtt_config_entry_data = {
1158
+ mqtt.CONF_BROKER: "mock-broker",
1159
+ mqtt.CONF_PROTOCOL: "5",
1160
+ }
1150
1161
  if mqtt_config_entry_options is None:
1151
1162
  mqtt_config_entry_options = {mqtt.CONF_BIRTH_MESSAGE: {}}
1152
1163
 
@@ -1636,7 +1647,8 @@ def recorder_db_url(
1636
1647
  # to ensure that InnoDB does not deadlock.
1637
1648
  with engine.begin() as connection:
1638
1649
  query = sa.text(
1639
- "select id FROM information_schema.processlist WHERE db=:db and id != CONNECTION_ID()"
1650
+ "select id FROM information_schema.processlist"
1651
+ " WHERE db=:db and id != CONNECTION_ID()"
1640
1652
  )
1641
1653
  rows = connection.execute(query, parameters={"db": db}).fetchall()
1642
1654
  if rows:
@@ -1966,6 +1978,15 @@ async def mock_enable_bluetooth(
1966
1978
  def mock_bluetooth_adapters() -> Generator[None]:
1967
1979
  """Fixture to mock bluetooth adapters."""
1968
1980
  with (
1981
+ # Simulate the Bluetooth management API being unavailable, as it is on
1982
+ # CI and most dev machines. Letting the real setup() run would attempt
1983
+ # real socket I/O on Linux hosts that do have BlueZ available, and
1984
+ # mocking it as successful would enable the advertising side channel,
1985
+ # changing the scanner code path the existing tests were written for.
1986
+ patch(
1987
+ "habluetooth.channels.bluez.MGMTBluetoothCtl.setup",
1988
+ AsyncMock(side_effect=OSError),
1989
+ ),
1969
1990
  patch("habluetooth.util.recover_adapter"),
1970
1991
  patch("bluetooth_auto_recovery.recover_adapter"),
1971
1992
  patch("bluetooth_adapters.systems.platform.system", return_value="Linux"),
@@ -2228,7 +2249,8 @@ _real_dhcp_service_info_init = DhcpServiceInfo.__init__
2228
2249
  def _dhcp_service_info_init(self: DhcpServiceInfo, *args: Any, **kwargs: Any) -> None:
2229
2250
  """Override __init__ for DhcpServiceInfo.
2230
2251
 
2231
- Ensure that the macaddress is always in lowercase and without colons to match DHCP service.
2252
+ Ensure that the macaddress is always in lowercase and
2253
+ without colons to match DHCP service.
2232
2254
  """
2233
2255
  _real_dhcp_service_info_init(self, *args, **kwargs)
2234
2256
  if self.macaddress != self.macaddress.lower().replace(":", ""):
@@ -4,8 +4,6 @@ Home Assistant extension for Syrupy.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  from contextlib import suppress
10
8
  import dataclasses
11
9
  from enum import IntFlag
@@ -248,8 +246,10 @@ class _IntFlagWrapper:
248
246
  self._flag = flag
249
247
 
250
248
  def __repr__(self) -> str:
251
- # 3.10: <ClimateEntityFeature.SWING_MODE|PRESET_MODE|FAN_MODE|TARGET_TEMPERATURE: 57>
252
- # 3.11: <ClimateEntityFeature.TARGET_TEMPERATURE|FAN_MODE|PRESET_MODE|SWING_MODE: 57>
249
+ # 3.10:
250
+ # <ClimateEntityFeature.SWING_MODE|PRESET_MODE|FAN_MODE|TARGET_TEMPERATURE: 57>
251
+ # 3.11:
252
+ # <ClimateEntityFeature.TARGET_TEMPERATURE|FAN_MODE|PRESET_MODE|SWING_MODE: 57>
253
253
  # Syrupy: <ClimateEntityFeature: 57>
254
254
  return f"<{self._flag.__class__.__name__}: {self._flag.value}>"
255
255
 
@@ -370,7 +370,7 @@ def _merge_serialized_report(report: SnapshotReport, json_data: dict[str, Any])
370
370
  for key, selected_item in json_data["_selected_items"].items():
371
371
  if key in report.selected_items:
372
372
  status = ItemStatus(selected_item)
373
- if status != ItemStatus.NOT_RUN:
373
+ if status is not ItemStatus.NOT_RUN:
374
374
  report.selected_items[key] = status
375
375
  else:
376
376
  report.selected_items[key] = ItemStatus(selected_item)
@@ -4,8 +4,6 @@ Typing helpers for Home Assistant tests.
4
4
  This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
5
5
  """
6
6
 
7
- from __future__ import annotations
8
-
9
7
  from collections.abc import Callable, Coroutine
10
8
  from contextlib import AbstractAsyncContextManager
11
9
  from typing import TYPE_CHECKING, Any
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-homeassistant-custom-component
3
- Version: 0.13.332
3
+ Version: 0.13.334
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
@@ -18,12 +18,12 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  License-File: LICENSE_HA_CORE.md
20
20
  Requires-Dist: sqlalchemy
21
- Requires-Dist: coverage==7.13.5
21
+ Requires-Dist: ast-serialize==0.3.0
22
+ Requires-Dist: coverage==7.14.0
22
23
  Requires-Dist: freezegun==1.5.5
23
24
  Requires-Dist: license-expression==30.4.3
24
25
  Requires-Dist: mock-open==1.4.0
25
- Requires-Dist: pydantic==2.13.2
26
- Requires-Dist: pylint-per-file-ignores==3.2.1
26
+ Requires-Dist: pydantic==2.13.4
27
27
  Requires-Dist: pipdeptree==2.26.1
28
28
  Requires-Dist: pytest-asyncio==1.3.0
29
29
  Requires-Dist: pytest-aiohttp==1.1.0
@@ -36,11 +36,13 @@ Requires-Dist: pytest-unordered==0.7.0
36
36
  Requires-Dist: pytest-picked==0.5.1
37
37
  Requires-Dist: pytest-xdist==3.8.0
38
38
  Requires-Dist: pytest==9.0.3
39
+ Requires-Dist: requests==2.34.2
39
40
  Requires-Dist: requests-mock==1.12.1
40
41
  Requires-Dist: respx==0.23.1
41
- Requires-Dist: syrupy==5.1.0
42
+ Requires-Dist: syrupy==5.2.0
42
43
  Requires-Dist: tqdm==4.67.1
43
- Requires-Dist: homeassistant==2026.5.3
44
+ Requires-Dist: unidiff==0.7.5
45
+ Requires-Dist: homeassistant==2026.6.0b0
44
46
  Requires-Dist: aiohasupervisor==0.4.3
45
47
  Requires-Dist: SQLAlchemy==2.0.49
46
48
  Requires-Dist: paho-mqtt==2.1.0
@@ -59,7 +61,7 @@ Dynamic: summary
59
61
 
60
62
  # pytest-homeassistant-custom-component
61
63
 
62
- ![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2026.5.3&labelColor=blue)
64
+ ![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2026.6.0b0&labelColor=blue)
63
65
 
64
66
  Package to automatically extract testing plugins from Home Assistant for custom component testing.
65
67
  The goal is to provide the same functionality as the tests in home-assistant/core.
@@ -1,10 +1,10 @@
1
1
  sqlalchemy
2
- coverage==7.13.5
2
+ ast-serialize==0.3.0
3
+ coverage==7.14.0
3
4
  freezegun==1.5.5
4
5
  license-expression==30.4.3
5
6
  mock-open==1.4.0
6
- pydantic==2.13.2
7
- pylint-per-file-ignores==3.2.1
7
+ pydantic==2.13.4
8
8
  pipdeptree==2.26.1
9
9
  pytest-asyncio==1.3.0
10
10
  pytest-aiohttp==1.1.0
@@ -17,11 +17,13 @@ pytest-unordered==0.7.0
17
17
  pytest-picked==0.5.1
18
18
  pytest-xdist==3.8.0
19
19
  pytest==9.0.3
20
+ requests==2.34.2
20
21
  requests-mock==1.12.1
21
22
  respx==0.23.1
22
- syrupy==5.1.0
23
+ syrupy==5.2.0
23
24
  tqdm==4.67.1
24
- homeassistant==2026.5.3
25
+ unidiff==0.7.5
26
+ homeassistant==2026.6.0b0
25
27
  aiohasupervisor==0.4.3
26
28
  SQLAlchemy==2.0.49
27
29
  paho-mqtt==2.1.0