Repository: home-assistant/core
Pull request #143613: ESPHome quality improvements round 2
Author: login: bdraco
Labels: name: cla-signed
name: small-pr
description: PRs with less than 30 lines.
name: integration: esphome
name: has-tests
name: by-code-owner
name: code-quality
name: Quality Scale: No score
Created: 2025-04-24T19:28:47Z | Merged: 2025-04-24T19:51:33Z



## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue in the
  additional information section.
-->
ESPHome quality improvements round 2
https://github.com/home-assistant/core/pull/143033

## Type of change
<!--
  What type of change does your PR introduce to Home Assistant?
  NOTE: Please, check only 1! box!
  If your PR requires multiple boxes to be checked, you'll most likely need to
  split it into multiple PRs. This makes things easier and faster to code review.
-->

- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [ ] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [x] Code quality improvements to existing code or addition of tests

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #
- This PR is related to issue: 
- Link to documentation pull request: 
- Link to developer documentation pull request: 
- Link to frontend pull request: 

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [ ] The code change is tested and works locally.
- [ ] Local tests pass. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
- [ ] I have followed the [development checklist][dev-checklist]
- [ ] I have followed the [perfect PR recommendations][perfect-pr]
- [ ] The code has been formatted using Ruff (`ruff format homeassistant tests`)
- [x] Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]

If the code communicates with devices, web services, or third-party tools:

- [ ] The [manifest file][manifest-docs] has all fields filled out correctly.  
      Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`.  
      Updated by running `python3 -m script.gen_requirements_all`.
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

<!--
  This project is very active and we have a high turnover of pull requests.

  Unfortunately, the number of incoming pull requests is higher than what our
  reviewers can review and merge so there is a long backlog of pull requests
  waiting for review. You can help here!
  
  By reviewing another pull request, you will help raise the code quality of
  that pull request and the final review will be faster. This way the general
  pace of pull request reviews will go up and your wait time will go down.
  
  When picking a pull request to review, try to choose one that hasn't yet
  been reviewed.

  Thanks for helping out!
-->

To help with the load of incoming pull requests:

- [ ] I have reviewed two other [open pull requests][prs] in this repository.

[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone+-status%3Afailure

<!--
  Thank you for contributing <3

  Below, some useful links you could explore:
-->
[dev-checklist]: https://developers.home-assistant.io/docs/development_checklist/
[manifest-docs]: https://developers.home-assistant.io/docs/creating_integration_manifest/
[quality-scale]: https://developers.home-assistant.io/docs/integration_quality_scale_index/
[docs-repository]: https://github.com/home-assistant/home-assistant.io
[perfect-pr]: https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr



Diff:
diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py
index 3c1499cf1ffc5a..d8d827f18a1006 100644
--- a/homeassistant/components/esphome/light.py
+++ b/homeassistant/components/esphome/light.py
@@ -109,7 +109,7 @@ def _mired_to_kelvin(mired_temperature: float) -> int:
 def _color_mode_to_ha(mode: int) -> str:
     """Convert an esphome color mode to a HA color mode constant.
 
-    Chose the color mode that best matches the feature-set.
+    Choose the color mode that best matches the feature-set.
     """
     candidates = []
     for ha_mode, cap_lists in _COLOR_MODE_MAPPING.items():
diff --git a/homeassistant/components/esphome/media_player.py b/homeassistant/components/esphome/media_player.py
index b05a453aca239a..3af6c0b2049075 100644
--- a/homeassistant/components/esphome/media_player.py
+++ b/homeassistant/components/esphome/media_player.py
@@ -96,7 +96,7 @@ def is_volume_muted(self) -> bool:
 
     @property
     @esphome_float_state_property
-    def volume_level(self) -> float | None:
+    def volume_level(self) -> float:
         """Volume level of the media player (0..1)."""
         return self._state.volume
 
diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py
index 96b2a426869d60..35edbf678adfe8 100644
--- a/homeassistant/components/esphome/switch.py
+++ b/homeassistant/components/esphome/switch.py
@@ -36,7 +36,7 @@ def _on_static_info_update(self, static_info: EntityInfo) -> None:
 
     @property
     @esphome_state_property
-    def is_on(self) -> bool | None:
+    def is_on(self) -> bool:
         """Return true if the switch is on."""
         return self._state.state
 
diff --git a/homeassistant/components/esphome/update.py b/homeassistant/components/esphome/update.py
index 9125e92a552ee2..d2c8d9dc3d05ae 100644
--- a/homeassistant/components/esphome/update.py
+++ b/homeassistant/components/esphome/update.py
@@ -109,7 +109,6 @@ class ESPHomeDashboardUpdateEntity(
     _attr_has_entity_name = True
     _attr_device_class = UpdateDeviceClass.FIRMWARE
     _attr_title = "ESPHome"
-    _attr_name = "Firmware"
     _attr_release_url = "https://esphome.io/changelog/"
     _attr_entity_registry_enabled_default = False
 
@@ -242,7 +241,7 @@ def _on_static_info_update(self, static_info: EntityInfo) -> None:
 
     @property
     @esphome_state_property
-    def installed_version(self) -> str | None:
+    def installed_version(self) -> str:
         """Return the installed version."""
         return self._state.current_version
 
@@ -260,19 +259,19 @@ def latest_version(self) -> str | None:
 
     @property
     @esphome_state_property
-    def release_summary(self) -> str | None:
+    def release_summary(self) -> str:
         """Return the release summary."""
         return self._state.release_summary
 
     @property
     @esphome_state_property
-    def release_url(self) -> str | None:
+    def release_url(self) -> str:
         """Return the release URL."""
         return self._state.release_url
 
     @property
     @esphome_state_property
-    def title(self) -> str | None:
+    def title(self) -> str:
         """Return the title of the update."""
         return self._state.title
 
diff --git a/homeassistant/components/esphome/valve.py b/homeassistant/components/esphome/valve.py
index e366fc08d19f01..f71a253c1f1d34 100644
--- a/homeassistant/components/esphome/valve.py
+++ b/homeassistant/components/esphome/valve.py
@@ -65,7 +65,7 @@ def is_closing(self) -> bool:
 
     @property
     @esphome_state_property
-    def current_valve_position(self) -> int | None:
+    def current_valve_position(self) -> int:
         """Return current position of valve. 0 is closed, 100 is open."""
         return round(self._state.position * 100.0)
 
diff --git a/tests/components/esphome/common.py b/tests/components/esphome/common.py
new file mode 100644
index 00000000000000..39661c0f340916
--- /dev/null
+++ b/tests/components/esphome/common.py
@@ -0,0 +1,34 @@
+"""ESPHome test common code."""
+
+from homeassistant.components import assist_satellite
+from homeassistant.components.assist_satellite import AssistSatelliteEntity
+
+# pylint: disable-next=hass-component-root-import
+from homeassistant.components.esphome import DOMAIN
+from homeassistant.components.esphome.assist_satellite import EsphomeAssistSatellite
+from homeassistant.const import Platform
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers import entity_registry as er
+from homeassistant.helpers.entity_component import EntityComponent
+
+
+def get_satellite_entity(
+    hass: HomeAssistant, mac_address: str
+) -> EsphomeAssistSatellite | None:
+    """Get the satellite entity for a device."""
+    ent_reg = er.async_get(hass)
+    satellite_entity_id = ent_reg.async_get_entity_id(
+        Platform.ASSIST_SATELLITE, DOMAIN, f"{mac_address}-assist_satellite"
+    )
+    if satellite_entity_id is None:
+        return None
+    assert satellite_entity_id.endswith("_assist_satellite")
+
+    component: EntityComponent[AssistSatelliteEntity] = hass.data[
+        assist_satellite.DOMAIN
+    ]
+    if (entity := component.get_entity(satellite_entity_id)) is not None:
+        assert isinstance(entity, EsphomeAssistSatellite)
+        return entity
+
+    return None
diff --git a/tests/components/esphome/test_assist_satellite.py b/tests/components/esphome/test_assist_satellite.py
index c072e5fda4ae0c..dddbbcc45f1411 100644
--- a/tests/components/esphome/test_assist_satellite.py
+++ b/tests/components/esphome/test_assist_satellite.py
@@ -34,59 +34,28 @@
 from homeassistant.components.assist_pipeline import PipelineEvent, PipelineEventType
 from homeassistant.components.assist_satellite import (
     AssistSatelliteConfiguration,
-    AssistSatelliteEntity,
     AssistSatelliteEntityFeature,
     AssistSatelliteWakeWord,
 )
 
 # pylint: disable-next=hass-component-root-import
 from homeassistant.components.assist_satellite.entity import AssistSatelliteState
-from homeassistant.components.esphome import DOMAIN
-from homeassistant.components.esphome.assist_satellite import (
-    EsphomeAssistSatellite,
-    VoiceAssistantUDPServer,
-)
+from homeassistant.components.esphome.assist_satellite import VoiceAssistantUDPServer
 from homeassistant.components.select import (
     DOMAIN as SELECT_DOMAIN,
     SERVICE_SELECT_OPTION,
 )
-from homeassistant.const import STATE_UNAVAILABLE, Platform
+from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
 from homeassistant.core import HomeAssistant
-from homeassistant.helpers import (
-    device_registry as dr,
-    entity_registry as er,
-    intent as intent_helper,
-)
-from homeassistant.helpers.entity_component import EntityComponent
+from homeassistant.helpers import device_registry as dr, intent as intent_helper
 from homeassistant.helpers.network import get_url
 
+from .common import get_satellite_entity
 from .conftest import MockESPHomeDevice
 
 from tests.components.tts.common import MockResultStream
 
 
-def get_satellite_entity(
-    hass: HomeAssistant, mac_address: str
-) -> EsphomeAssistSatellite | None:
-    """Get the satellite entity for a device."""
-    ent_reg = er.async_get(hass)
-    satellite_entity_id = ent_reg.async_get_entity_id(
-        Platform.ASSIST_SATELLITE, DOMAIN, f"{mac_address}-assist_satellite"
-    )
-    if satellite_entity_id is None:
-        return None
-    assert satellite_entity_id.endswith("_assist_satellite")
-
-    component: EntityComponent[AssistSatelliteEntity] = hass.data[
-        assist_satellite.DOMAIN
-    ]
-    if (entity := component.get_entity(satellite_entity_id)) is not None:
-        assert isinstance(entity, EsphomeAssistSatellite)
-        return entity
-
-    return None
-
-
 @pytest.fixture
 def mock_wav() -> bytes:
     """Return test WAV audio."""
@@ -1143,32 +1112,6 @@ async def test_tts_minimal_format_from_media_player(
         }
 
 
-async def test_announce_supported_features(
-    hass: HomeAssistant,
-    mock_client: APIClient,
-    mock_esphome_device: Callable[
-        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
-        Awaitable[MockESPHomeDevice],
-    ],
-) -> None:
-    """Test that the announce supported feature is not set by default."""
-    mock_device: MockESPHomeDevice = await mock_esphome_device(
-        mock_client=mock_client,
-        entity_info=[],
-        user_service=[],
-        states=[],
-        device_info={
-            "voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
-        },
-    )
-    await hass.async_block_till_done()
-
-    satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
-    assert satellite is not None
-
-    assert not (satellite.supported_features & AssistSatelliteEntityFeature.ANNOUNCE)
-
-
 async def test_announce_message(
     hass: HomeAssistant,
     mock_client: APIClient,
@@ -1236,7 +1179,7 @@ async def send_voice_assistant_announcement_await_response(
                 assist_satellite.DOMAIN,
                 "announce",
                 {
-                    "entity_id": satellite.entity_id,
+                    ATTR_ENTITY_ID: satellite.entity_id,
                     "message": "test-text",
                     "preannounce": False,
                 },
@@ -1326,7 +1269,7 @@ async def send_voice_assistant_announcement_await_response(
                 assist_satellite.DOMAIN,
                 "announce",
                 {
-                    "entity_id": satellite.entity_id,
+                    ATTR_ENTITY_ID: satellite.entity_id,
                     "media_id": "https://www.home-assistant.io/resolved.mp3",
                     "preannounce": False,
                 },
@@ -1413,7 +1356,7 @@ async def send_voice_assistant_announcement_await_response(
                 assist_satellite.DOMAIN,
                 "announce",
                 {
-                    "entity_id": satellite.entity_id,
+                    ATTR_ENTITY_ID: satellite.entity_id,
                     "message": "test-text",
                     "preannounce_media_id": "test-preannounce",
                 },
@@ -1423,7 +1366,7 @@ async def send_voice_assistant_announcement_await_response(
             assert satellite.state == AssistSatelliteState.IDLE
 
 
-async def test_start_conversation_supported_features(
+async def test_non_default_supported_features(
     hass: HomeAssistant,
     mock_client: APIClient,
     mock_esphome_device: Callable[
@@ -1431,7 +1374,7 @@ async def test_start_conversation_supported_features(
         Awaitable[MockESPHomeDevice],
     ],
 ) -> None:
-    """Test that the start conversation supported feature is not set by default."""
+    """Test that the start conversation and announce are not set by default."""
     mock_device: MockESPHomeDevice = await mock_esphome_device(
         mock_client=mock_client,
         entity_info=[],
@@ -1449,6 +1392,7 @@ async def test_start_conversation_supported_features(
     assert not (
         satellite.supported_features & AssistSatelliteEntityFeature.START_CONVERSATION
     )
+    assert not (satellite.supported_features & AssistSatelliteEntityFeature.ANNOUNCE)
 
 
 async def test_start_conversation_message(
@@ -1537,7 +1481,7 @@ async def send_voice_assistant_announcement_await_response(
                 assist_satellite.DOMAIN,
                 "start_conversation",
                 {
-                    "entity_id": satellite.entity_id,
+                    ATTR_ENTITY_ID: satellite.entity_id,
                     "start_message": "test-text",
                     "preannounce": False,
                 },
@@ -1646,7 +1590,7 @@ async def send_voice_assistant_announcement_await_response(
                 assist_satellite.DOMAIN,
                 "start_conversation",
                 {
-                    "entity_id": satellite.entity_id,
+                    ATTR_ENTITY_ID: satellite.entity_id,
                     "start_media_id": "https://www.home-assistant.io/resolved.mp3",
                     "preannounce": False,
                 },
@@ -1752,7 +1696,7 @@ async def send_voice_assistant_announcement_await_response(
                 assist_satellite.DOMAIN,
                 "start_conversation",
                 {
-                    "entity_id": satellite.entity_id,
+                    ATTR_ENTITY_ID: satellite.entity_id,
                     "start_message": "test-text",
                     "preannounce_media_id": "test-preannounce",
                 },
@@ -1982,7 +1926,7 @@ async def wrapper(*args, **kwargs):
     await hass.services.async_call(
         SELECT_DOMAIN,
         SERVICE_SELECT_OPTION,
-        {"entity_id": "select.test_wake_word", "option": "Okay Nabu"},
+        {ATTR_ENTITY_ID: "select.test_wake_word", "option": "Okay Nabu"},
         blocking=True,
     )
     await hass.async_block_till_done()
@@ -1997,122 +1941,3 @@ async def wrapper(*args, **kwargs):
 
     # Satellite config should have been updated
     assert satellite.async_get_configuration().active_wake_words == ["okay_nabu"]
-
-
-async def test_wake_word_select_no_wake_words(
-    hass: HomeAssistant,
-    mock_client: APIClient,
-    mock_esphome_device: Callable[
-        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
-        Awaitable[MockESPHomeDevice],
-    ],
-) -> None:
-    """Test wake word select is unavailable when there are no available wake word."""
-    device_config = AssistSatelliteConfiguration(
-        available_wake_words=[],
-        active_wake_words=[],
-        max_active_wake_words=1,
-    )
-    mock_client.get_voice_assistant_configuration.return_value = device_config
-
-    mock_device: MockESPHomeDevice = await mock_esphome_device(
-        mock_client=mock_client,
-        entity_info=[],
-        user_service=[],
-        states=[],
-        device_info={
-            "voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
-            | VoiceAssistantFeature.ANNOUNCE
-        },
-    )
-    await hass.async_block_till_done()
-
-    satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
-    assert satellite is not None
-    assert not satellite.async_get_configuration().available_wake_words
-
-    # Select should be unavailable
-    state = hass.states.get("select.test_wake_word")
-    assert state is not None
-    assert state.state == STATE_UNAVAILABLE
-
-
-async def test_wake_word_select_zero_max_wake_words(
-    hass: HomeAssistant,
-    mock_client: APIClient,
-    mock_esphome_device: Callable[
-        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
-        Awaitable[MockESPHomeDevice],
-    ],
-) -> None:
-    """Test wake word select is unavailable max wake words is zero."""
-    device_config = AssistSatelliteConfiguration(
-        available_wake_words=[
-            AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
-        ],
-        active_wake_words=[],
-        max_active_wake_words=0,
-    )
-    mock_client.get_voice_assistant_configuration.return_value = device_config
-
-    mock_device: MockESPHomeDevice = await mock_esphome_device(
-        mock_client=mock_client,
-        entity_info=[],
-        user_service=[],
-        states=[],
-        device_info={
-            "voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
-            | VoiceAssistantFeature.ANNOUNCE
-        },
-    )
-    await hass.async_block_till_done()
-
-    satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
-    assert satellite is not None
-    assert satellite.async_get_configuration().max_active_wake_words == 0
-
-    # Select should be unavailable
-    state = hass.states.get("select.test_wake_word")
-    assert state is not None
-    assert state.state == STATE_UNAVAILABLE
-
-
-async def test_wake_word_select_no_active_wake_words(
-    hass: HomeAssistant,
-    mock_client: APIClient,
-    mock_esphome_device: Callable[
-        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
-        Awaitable[MockESPHomeDevice],
-    ],
-) -> None:
-    """Test wake word select uses first available wake word if none are active."""
-    device_config = AssistSatelliteConfiguration(
-        available_wake_words=[
-            AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
-            AssistSatelliteWakeWord("hey_jarvis", "Hey Jarvis", ["en"]),
-        ],
-        active_wake_words=[],
-        max_active_wake_words=1,
-    )
-    mock_client.get_voice_assistant_configuration.return_value = device_config
-
-    mock_device: MockESPHomeDevice = await mock_esphome_device(
-        mock_client=mock_client,
-        entity_info=[],
-        user_service=[],
-        states=[],
-        device_info={
-            "voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
-            | VoiceAssistantFeature.ANNOUNCE
-        },
-    )
-    await hass.async_block_till_done()
-
-    satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
-    assert satellite is not None
-    assert not satellite.async_get_configuration().active_wake_words
-
-    # First available wake word should be selected
-    state = hass.states.get("select.test_wake_word")
-    assert state is not None
-    assert state.state == "Okay Nabu"
diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py
index 752f980cd87782..3e58244707d83d 100644
--- a/tests/components/esphome/test_config_flow.py
+++ b/tests/components/esphome/test_config_flow.py
@@ -72,18 +72,16 @@ async def test_user_connection_works(
 ) -> None:
     """Test we can finish a config flow."""
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
-        data=None,
     )
 
     assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "user"
 
-    result = await hass.config_entries.flow.async_init(
-        "esphome",
-        context={"source": config_entries.SOURCE_USER},
-        data={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
     )
 
     assert result["type"] is FlowResultType.CREATE_ENTRY
@@ -119,7 +117,7 @@ async def test_user_connection_updates_host(hass: HomeAssistant) -> None:
     )
     entry.add_to_hass(hass)
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data=None,
     )
@@ -127,10 +125,9 @@ async def test_user_connection_updates_host(hass: HomeAssistant) -> None:
     assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "user"
 
-    result = await hass.config_entries.flow.async_init(
-        "esphome",
-        context={"source": config_entries.SOURCE_USER},
-        data={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
+    result = await hass.config_entries.flow.async_configure(
+        result["flow_id"],
+        user_input={CONF_HOST: "127.0.0.1", CONF_PORT: 80},
     )
     assert result["type"] is FlowResultType.ABORT
     assert result["reason"] == "already_configured_updates"
@@ -157,7 +154,7 @@ async def test_user_sets_unique_id(hass: HomeAssistant) -> None:
         type="mock_type",
     )
     discovery_result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
 
     assert discovery_result["type"] is FlowResultType.FORM
@@ -180,7 +177,7 @@ async def test_user_sets_unique_id(hass: HomeAssistant) -> None:
     }
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data=None,
     )
@@ -211,7 +208,7 @@ async def test_user_resolve_error(hass: HomeAssistant, mock_client: APIClient) -
     ) as exc:
         mock_client.device_info.side_effect = exc
         result = await hass.config_entries.flow.async_init(
-            "esphome",
+            DOMAIN,
             context={"source": config_entries.SOURCE_USER},
             data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
         )
@@ -258,7 +255,7 @@ async def test_user_causes_zeroconf_to_abort(hass: HomeAssistant) -> None:
         type="mock_type",
     )
     discovery_result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
 
     assert discovery_result["type"] is FlowResultType.FORM
@@ -268,7 +265,7 @@ async def test_user_causes_zeroconf_to_abort(hass: HomeAssistant) -> None:
     }
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data=None,
     )
@@ -301,7 +298,7 @@ async def test_user_connection_error(
     mock_client.device_info.side_effect = APIConnectionError
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -342,7 +339,7 @@ async def test_user_with_password(
     mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -374,7 +371,7 @@ async def test_user_invalid_password(
     mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -432,7 +429,7 @@ async def test_user_dashboard_has_wrong_key(
         return_value=WRONG_NOISE_PSK,
     ):
         result = await hass.config_entries.flow.async_init(
-            "esphome",
+            DOMAIN,
             context={"source": config_entries.SOURCE_USER},
             data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
         )
@@ -487,7 +484,7 @@ async def test_user_discovers_name_and_gets_key_from_dashboard(
         return_value=VALID_NOISE_PSK,
     ):
         result = await hass.config_entries.flow.async_init(
-            "esphome",
+            DOMAIN,
             context={"source": config_entries.SOURCE_USER},
             data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
         )
@@ -539,7 +536,7 @@ async def test_user_discovers_name_and_gets_key_from_dashboard_fails(
         side_effect=dashboard_exception,
     ):
         result = await hass.config_entries.flow.async_init(
-            "esphome",
+            DOMAIN,
             context={"source": config_entries.SOURCE_USER},
             data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
         )
@@ -589,12 +586,12 @@ async def test_user_discovers_name_and_dashboard_is_unavailable(
     )
 
     with patch(
-        "esphome_dashboard_api.ESPHomeDashboardAPI.get_devices",
+        "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
         side_effect=TimeoutError,
     ):
         await dashboard.async_get_dashboard(hass).async_refresh()
         result = await hass.config_entries.flow.async_init(
-            "esphome",
+            DOMAIN,
             context={"source": config_entries.SOURCE_USER},
             data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
         )
@@ -627,7 +624,7 @@ async def test_login_connection_error(
     mock_client.device_info.return_value = DeviceInfo(uses_password=True, name="test")
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -680,7 +677,7 @@ async def test_discovery_initiation(hass: HomeAssistant) -> None:
         type="mock_type",
     )
     flow = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
     assert get_flow_context(hass, flow) == {
         "source": config_entries.SOURCE_ZEROCONF,
@@ -714,7 +711,7 @@ async def test_discovery_no_mac(hass: HomeAssistant) -> None:
         type="mock_type",
     )
     flow = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
     assert flow["type"] is FlowResultType.ABORT
     assert flow["reason"] == "mdns_missing_mac"
@@ -741,7 +738,7 @@ async def test_discovery_already_configured(hass: HomeAssistant) -> None:
         type="mock_type",
     )
     result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
 
     assert result["type"] is FlowResultType.ABORT
@@ -767,14 +764,14 @@ async def test_discovery_duplicate_data(hass: HomeAssistant) -> None:
     )
 
     result = await hass.config_entries.flow.async_init(
-        "esphome", data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
+        DOMAIN, data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
     )
     assert result["type"] is FlowResultType.FORM
     assert result["step_id"] == "discovery_confirm"
     assert result["description_placeholders"] == {"name": "test"}
 
     result = await hass.config_entries.flow.async_init(
-        "esphome", data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
+        DOMAIN, data=service_info, context={"source": config_entries.SOURCE_ZEROCONF}
     )
     assert result["type"] is FlowResultType.ABORT
     assert result["reason"] == "already_in_progress"
@@ -801,7 +798,7 @@ async def test_discovery_updates_unique_id(hass: HomeAssistant) -> None:
         type="mock_type",
     )
     result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
 
     assert result["type"] is FlowResultType.ABORT
@@ -821,7 +818,7 @@ async def test_user_requires_psk(hass: HomeAssistant, mock_client: APIClient) ->
     mock_client.device_info.side_effect = RequiresEncryptionAPIError
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -867,7 +864,7 @@ async def test_encryption_key_valid_psk(
     mock_client.device_info.side_effect = RequiresEncryptionAPIError
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -903,7 +900,7 @@ async def test_encryption_key_invalid_psk(
     mock_client.device_info.side_effect = RequiresEncryptionAPIError
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -1301,7 +1298,7 @@ async def test_discovery_dhcp_updates_host(
         macaddress="1122334455aa",
     )
     result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
     )
 
     assert result["type"] is FlowResultType.ABORT
@@ -1337,7 +1334,7 @@ async def test_discovery_dhcp_does_not_update_host_wrong_mac(
         macaddress="1122334455aa",
     )
     result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
     )
 
     assert result["type"] is FlowResultType.ABORT
@@ -1372,7 +1369,7 @@ async def test_discovery_dhcp_does_not_update_host_wrong_mac_bad_key(
         macaddress="1122334455aa",
     )
     result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
     )
 
     assert result["type"] is FlowResultType.ABORT
@@ -1407,7 +1404,7 @@ async def test_discovery_dhcp_does_not_update_host_missing_mac_bad_key(
         macaddress="1122334455aa",
     )
     result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
     )
 
     assert result["type"] is FlowResultType.ABORT
@@ -1441,7 +1438,7 @@ async def test_discovery_dhcp_no_changes(
         macaddress="000000000000",
     )
     result = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_DHCP}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=service_info
     )
 
     assert result["type"] is FlowResultType.ABORT
@@ -1454,7 +1451,7 @@ async def test_discovery_dhcp_no_changes(
 async def test_discovery_hassio(hass: HomeAssistant) -> None:
     """Test dashboard discovery."""
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         data=HassioServiceInfo(
             config={
                 "host": "mock-esphome",
@@ -1494,7 +1491,7 @@ async def test_zeroconf_encryption_key_via_dashboard(
         type="mock_type",
     )
     flow = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
 
     assert flow["type"] is FlowResultType.FORM
@@ -1561,7 +1558,7 @@ async def test_zeroconf_encryption_key_via_dashboard_with_api_encryption_prop(
         type="mock_type",
     )
     flow = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
 
     assert flow["type"] is FlowResultType.FORM
@@ -1625,7 +1622,7 @@ async def test_zeroconf_no_encryption_key_via_dashboard(
         type="mock_type",
     )
     flow = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=service_info
     )
 
     assert flow["type"] is FlowResultType.FORM
@@ -1767,7 +1764,7 @@ async def test_user_discovers_name_no_dashboard(
     ]
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -1805,7 +1802,7 @@ async def mqtt_discovery_test_abort(
         timestamp=None,
     )
     flow = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_MQTT}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_MQTT}, data=service_info
     )
     assert flow["type"] is FlowResultType.ABORT
     assert flow["reason"] == reason
@@ -1849,7 +1846,7 @@ async def test_discovery_mqtt_initiation(hass: HomeAssistant) -> None:
         timestamp=None,
     )
     flow = await hass.config_entries.flow.async_init(
-        "esphome", context={"source": config_entries.SOURCE_MQTT}, data=service_info
+        DOMAIN, context={"source": config_entries.SOURCE_MQTT}, data=service_info
     )
 
     result = await hass.config_entries.flow.async_configure(
@@ -1886,7 +1883,7 @@ async def test_user_flow_name_conflict_migrate(
     )
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
@@ -1936,11 +1933,10 @@ async def test_user_flow_name_conflict_overwrite(
     )
 
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053},
     )
-    await hass.async_block_till_done()
 
     assert result["type"] is FlowResultType.MENU
     assert result["step_id"] == "name_conflict"
diff --git a/tests/components/esphome/test_dashboard.py b/tests/components/esphome/test_dashboard.py
index 1f675a10b829d8..5fa53dc7f7522c 100644
--- a/tests/components/esphome/test_dashboard.py
+++ b/tests/components/esphome/test_dashboard.py
@@ -6,12 +6,7 @@
 from aioesphomeapi import DeviceInfo, InvalidAuthAPIError
 import pytest
 
-from homeassistant.components.esphome import (
-    CONF_NOISE_PSK,
-    DOMAIN,
-    coordinator,
-    dashboard,
-)
+from homeassistant.components.esphome import CONF_NOISE_PSK, DOMAIN, dashboard
 from homeassistant.config_entries import ConfigEntryState
 from homeassistant.core import HomeAssistant
 from homeassistant.data_entry_flow import FlowResultType
@@ -117,8 +112,9 @@ async def test_setup_dashboard_fails(
     hass_storage: dict[str, Any],
 ) -> None:
     """Test that nothing is stored on failed dashboard setup when there was no dashboard before."""
-    with patch.object(
-        coordinator.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
+    with patch(
+        "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
+        side_effect=TimeoutError,
     ) as mock_get_devices:
         await async_setup_component(hass, DOMAIN, {})
         await hass.async_block_till_done()
@@ -136,8 +132,8 @@ async def test_setup_dashboard_fails_when_already_setup(
     hass_storage: dict[str, Any],
 ) -> None:
     """Test failed dashboard setup still reloads entries if one existed before."""
-    with patch.object(
-        coordinator.ESPHomeDashboardAPI, "get_devices"
+    with patch(
+        "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices"
     ) as mock_get_devices:
         await dashboard.async_set_dashboard_info(
             hass, "test-slug", "working-host", 6052
@@ -151,8 +147,9 @@ async def test_setup_dashboard_fails_when_already_setup(
     await hass.async_block_till_done()
 
     with (
-        patch.object(
-            coordinator.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError
+        patch(
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
+            side_effect=TimeoutError,
         ) as mock_get_devices,
         patch(
             "homeassistant.components.esphome.async_setup_entry", return_value=True
diff --git a/tests/components/esphome/test_entity.py b/tests/components/esphome/test_entity.py
index 290b1871cd761d..1184b345d149d3 100644
--- a/tests/components/esphome/test_entity.py
+++ b/tests/components/esphome/test_entity.py
@@ -221,9 +221,6 @@ async def test_entities_removed_after_reload(
             unique_id="my_binary_sensor",
         ),
     ]
-    states = [
-        BinarySensorState(key=1, state=True, missing_state=False),
-    ]
     mock_device.client.list_entities_services = AsyncMock(
         return_value=(entity_info, user_service)
     )
diff --git a/tests/components/esphome/test_entry_data.py b/tests/components/esphome/test_entry_data.py
index a8535c38224605..61d0688e6410fa 100644
--- a/tests/components/esphome/test_entry_data.py
+++ b/tests/components/esphome/test_entry_data.py
@@ -7,6 +7,8 @@
     SensorState,
 )
 
+from homeassistant.components.esphome import DOMAIN
+from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers import entity_registry as er
 
@@ -62,15 +64,15 @@ async def test_migrate_entity_unique_id_downgrade_upgrade(
 ) -> None:
     """Test unique id migration prefers the original entity on downgrade upgrade."""
     entity_registry.async_get_or_create(
-        "sensor",
-        "esphome",
+        SENSOR_DOMAIN,
+        DOMAIN,
         "my_sensor",
         suggested_object_id="old_sensor",
         disabled_by=None,
     )
     entity_registry.async_get_or_create(
-        "sensor",
-        "esphome",
+        SENSOR_DOMAIN,
+        DOMAIN,
         "11:22:33:44:55:AA-sensor-mysensor",
         suggested_object_id="new_sensor",
         disabled_by=None,
@@ -103,7 +105,7 @@ async def test_migrate_entity_unique_id_downgrade_upgrade(
     # entity that was only created on downgrade and they keep
     # the original one.
     assert (
-        entity_registry.async_get_entity_id("sensor", "esphome", "my_sensor")
+        entity_registry.async_get_entity_id(SENSOR_DOMAIN, DOMAIN, "my_sensor")
         is not None
     )
     # Note that ESPHome includes the EntityInfo type in the unique id
diff --git a/tests/components/esphome/test_init.py b/tests/components/esphome/test_init.py
index 9e4c9709e7d453..7473734ff3e986 100644
--- a/tests/components/esphome/test_init.py
+++ b/tests/components/esphome/test_init.py
@@ -9,9 +9,9 @@
 from tests.common import MockConfigEntry
 
 
-@pytest.mark.usefixtures("mock_zeroconf")
-async def test_delete_entry(hass: HomeAssistant, mock_client) -> None:
-    """Test we can delete an entry with error."""
+@pytest.mark.usefixtures("mock_client", "mock_zeroconf")
+async def test_delete_entry(hass: HomeAssistant) -> None:
+    """Test we can delete an entry without error."""
     entry = MockConfigEntry(
         domain=DOMAIN,
         data={CONF_HOST: "test.local", CONF_PORT: 6053, CONF_PASSWORD: ""},
diff --git a/tests/components/esphome/test_light.py b/tests/components/esphome/test_light.py
index 8e4f37079d1b03..e713bbbe630677 100644
--- a/tests/components/esphome/test_light.py
+++ b/tests/components/esphome/test_light.py
@@ -38,6 +38,8 @@
 from homeassistant.const import ATTR_ENTITY_ID
 from homeassistant.core import HomeAssistant
 
+LIGHT_COLOR_CAPABILITY_UNKNOWN = 1 << 8  # 256
+
 
 async def test_light_on_off(
     hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
@@ -391,7 +393,9 @@ async def test_light_brightness_on_off_with_unknown_color_mode(
             min_mireds=153,
             max_mireds=400,
             supported_color_modes=[
-                LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS | 1 << 8
+                LightColorCapability.ON_OFF
+                | LightColorCapability.BRIGHTNESS
+                | LIGHT_COLOR_CAPABILITY_UNKNOWN
             ],
         )
     ]
@@ -420,7 +424,7 @@ async def test_light_brightness_on_off_with_unknown_color_mode(
                 state=True,
                 color_mode=LightColorCapability.ON_OFF
                 | LightColorCapability.BRIGHTNESS
-                | 1 << 8,
+                | LIGHT_COLOR_CAPABILITY_UNKNOWN,
             )
         ]
     )
@@ -439,7 +443,7 @@ async def test_light_brightness_on_off_with_unknown_color_mode(
                 state=True,
                 color_mode=LightColorCapability.ON_OFF
                 | LightColorCapability.BRIGHTNESS
-                | 1 << 8,
+                | LIGHT_COLOR_CAPABILITY_UNKNOWN,
                 brightness=pytest.approx(0.4980392156862745),
             )
         ]
diff --git a/tests/components/esphome/test_manager.py b/tests/components/esphome/test_manager.py
index 172b863229d9b8..652d2453e05fc8 100644
--- a/tests/components/esphome/test_manager.py
+++ b/tests/components/esphome/test_manager.py
@@ -34,6 +34,7 @@
     STABLE_BLE_VERSION_STR,
 )
 from homeassistant.components.esphome.manager import DEVICE_CONFLICT_ISSUE_FORMAT
+from homeassistant.components.tag import DOMAIN as TAG_DOMAIN
 from homeassistant.const import (
     CONF_HOST,
     CONF_PASSWORD,
@@ -192,7 +193,7 @@ async def test_esphome_device_service_calls_allowed(
     issue_registry: ir.IssueRegistry,
 ) -> None:
     """Test a device with service calls are allowed."""
-    await async_setup_component(hass, "tag", {})
+    await async_setup_component(hass, TAG_DOMAIN, {})
     entity_info = []
     states = []
     user_service = []
diff --git a/tests/components/esphome/test_select.py b/tests/components/esphome/test_select.py
index 6ae1260a89dbd1..e170a1a7f6dbf8 100644
--- a/tests/components/esphome/test_select.py
+++ b/tests/components/esphome/test_select.py
@@ -1,9 +1,22 @@
 """Test ESPHome selects."""
 
+from collections.abc import Awaitable, Callable
 from unittest.mock import call
 
-from aioesphomeapi import APIClient, SelectInfo, SelectState
+from aioesphomeapi import (
+    APIClient,
+    EntityInfo,
+    EntityState,
+    SelectInfo,
+    SelectState,
+    UserService,
+    VoiceAssistantFeature,
+)
 
+from homeassistant.components.assist_satellite import (
+    AssistSatelliteConfiguration,
+    AssistSatelliteWakeWord,
+)
 from homeassistant.components.select import (
     ATTR_OPTION,
     DOMAIN as SELECT_DOMAIN,
@@ -12,6 +25,9 @@
 from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE
 from homeassistant.core import HomeAssistant
 
+from .common import get_satellite_entity
+from .conftest import MockESPHomeDevice
+
 
 async def test_pipeline_selector(
     hass: HomeAssistant,
@@ -80,3 +96,122 @@ async def test_select_generic_entity(
         blocking=True,
     )
     mock_client.select_command.assert_has_calls([call(1, "b")])
+
+
+async def test_wake_word_select_no_wake_words(
+    hass: HomeAssistant,
+    mock_client: APIClient,
+    mock_esphome_device: Callable[
+        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
+        Awaitable[MockESPHomeDevice],
+    ],
+) -> None:
+    """Test wake word select is unavailable when there are no available wake word."""
+    device_config = AssistSatelliteConfiguration(
+        available_wake_words=[],
+        active_wake_words=[],
+        max_active_wake_words=1,
+    )
+    mock_client.get_voice_assistant_configuration.return_value = device_config
+
+    mock_device: MockESPHomeDevice = await mock_esphome_device(
+        mock_client=mock_client,
+        entity_info=[],
+        user_service=[],
+        states=[],
+        device_info={
+            "voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
+            | VoiceAssistantFeature.ANNOUNCE
+        },
+    )
+    await hass.async_block_till_done()
+
+    satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
+    assert satellite is not None
+    assert not satellite.async_get_configuration().available_wake_words
+
+    # Select should be unavailable
+    state = hass.states.get("select.test_wake_word")
+    assert state is not None
+    assert state.state == STATE_UNAVAILABLE
+
+
+async def test_wake_word_select_zero_max_wake_words(
+    hass: HomeAssistant,
+    mock_client: APIClient,
+    mock_esphome_device: Callable[
+        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
+        Awaitable[MockESPHomeDevice],
+    ],
+) -> None:
+    """Test wake word select is unavailable max wake words is zero."""
+    device_config = AssistSatelliteConfiguration(
+        available_wake_words=[
+            AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
+        ],
+        active_wake_words=[],
+        max_active_wake_words=0,
+    )
+    mock_client.get_voice_assistant_configuration.return_value = device_config
+
+    mock_device: MockESPHomeDevice = await mock_esphome_device(
+        mock_client=mock_client,
+        entity_info=[],
+        user_service=[],
+        states=[],
+        device_info={
+            "voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
+            | VoiceAssistantFeature.ANNOUNCE
+        },
+    )
+    await hass.async_block_till_done()
+
+    satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
+    assert satellite is not None
+    assert satellite.async_get_configuration().max_active_wake_words == 0
+
+    # Select should be unavailable
+    state = hass.states.get("select.test_wake_word")
+    assert state is not None
+    assert state.state == STATE_UNAVAILABLE
+
+
+async def test_wake_word_select_no_active_wake_words(
+    hass: HomeAssistant,
+    mock_client: APIClient,
+    mock_esphome_device: Callable[
+        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
+        Awaitable[MockESPHomeDevice],
+    ],
+) -> None:
+    """Test wake word select uses first available wake word if none are active."""
+    device_config = AssistSatelliteConfiguration(
+        available_wake_words=[
+            AssistSatelliteWakeWord("okay_nabu", "Okay Nabu", ["en"]),
+            AssistSatelliteWakeWord("hey_jarvis", "Hey Jarvis", ["en"]),
+        ],
+        active_wake_words=[],
+        max_active_wake_words=1,
+    )
+    mock_client.get_voice_assistant_configuration.return_value = device_config
+
+    mock_device: MockESPHomeDevice = await mock_esphome_device(
+        mock_client=mock_client,
+        entity_info=[],
+        user_service=[],
+        states=[],
+        device_info={
+            "voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
+            | VoiceAssistantFeature.ANNOUNCE
+        },
+    )
+    await hass.async_block_till_done()
+
+    satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
+    assert satellite is not None
+    assert not satellite.async_get_configuration().active_wake_words
+
+    # First available wake word should be selected
+    state = hass.states.get("select.test_wake_word")
+    assert state is not None
+    assert state.state == "Okay Nabu"
diff --git a/tests/components/esphome/test_update.py b/tests/components/esphome/test_update.py
index 910463f6e304b3..a461f32208881c 100644
--- a/tests/components/esphome/test_update.py
+++ b/tests/components/esphome/test_update.py
@@ -119,10 +119,12 @@ async def test_update_entity(
     # Compile failed, don't try to upload
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=False
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
+            return_value=False,
         ) as mock_compile,
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
+            return_value=True,
         ) as mock_upload,
         pytest.raises(
             HomeAssistantError,
@@ -130,9 +132,9 @@ async def test_update_entity(
         ),
     ):
         await hass.services.async_call(
-            "update",
-            "install",
-            {"entity_id": "update.test_firmware"},
+            UPDATE_DOMAIN,
+            SERVICE_INSTALL,
+            {ATTR_ENTITY_ID: "update.test_firmware"},
             blocking=True,
         )
 
@@ -144,10 +146,12 @@ async def test_update_entity(
     # Compile success, upload fails
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
+            return_value=True,
         ) as mock_compile,
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
+            return_value=False,
         ) as mock_upload,
         pytest.raises(
             HomeAssistantError,
@@ -155,9 +159,9 @@ async def test_update_entity(
         ),
     ):
         await hass.services.async_call(
-            "update",
-            "install",
-            {"entity_id": "update.test_firmware"},
+            UPDATE_DOMAIN,
+            SERVICE_INSTALL,
+            {ATTR_ENTITY_ID: "update.test_firmware"},
             blocking=True,
         )
 
@@ -170,16 +174,18 @@ async def test_update_entity(
     # Everything works
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
+            return_value=True,
         ) as mock_compile,
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
+            return_value=True,
         ) as mock_upload,
     ):
         await hass.services.async_call(
-            "update",
-            "install",
-            {"entity_id": "update.test_firmware"},
+            UPDATE_DOMAIN,
+            SERVICE_INSTALL,
+            {ATTR_ENTITY_ID: "update.test_firmware"},
             blocking=True,
         )
 
@@ -286,7 +292,7 @@ async def test_update_entity_dashboard_not_available_startup(
     """Test ESPHome update entity when dashboard is not available at startup."""
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.get_devices",
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
             side_effect=TimeoutError,
         ),
     ):
@@ -334,7 +340,7 @@ async def test_update_entity_dashboard_discovered_after_startup_but_update_faile
 ) -> None:
     """Test ESPHome update entity when dashboard is discovered after startup and the first update fails."""
     with patch(
-        "esphome_dashboard_api.ESPHomeDashboardAPI.get_devices",
+        "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.get_devices",
         side_effect=TimeoutError,
     ):
         await async_get_dashboard(hass).async_refresh()


Reviews:
user: login: joostlek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:32:06Z
user: login: joostlek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:37:00Z
user: login: joostlek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:37:27Z
user: login: joostlek
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T19:37:34Z
user: login: bdraco
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:39:14Z
user: login: bdraco
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:40:28Z
user: login: bdraco
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:40:38Z
user: login: bdraco
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:42:45Z
user: login: bdraco
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:43:18Z
user: login: bdraco
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:44:36Z
user: login: joostlek
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T19:45:35Z

Review comments:
diff_hunk: @@ -72,18 +72,17 @@ async def test_user_connection_works(
 ) -> None:
     """Test we can finish a config flow."""
     result = await hass.config_entries.flow.async_init(
-        "esphome",
+        DOMAIN,
         context={"source": config_entries.SOURCE_USER},
         data=None,
path: tests/components/esphome/test_config_flow.py
user: login: joostlek
body: ```suggestion
```
I think we generally never explicitly set None for the first data
created_at: 2025-04-24T19:32:06Z
author_association: MEMBER
diff_hunk: @@ -1,9 +1,24 @@
 """Test ESPHome selects."""
 
+from collections.abc import Awaitable, Callable
 from unittest.mock import call
 
-from aioesphomeapi import APIClient, SelectInfo, SelectState
+from aioesphomeapi import (
+    APIClient,
+    EntityInfo,
+    EntityState,
+    SelectInfo,
+    SelectState,
+    UserService,
+    VoiceAssistantFeature,
+)
+
+from homeassistant.components.assist_satellite import (
+    AssistSatelliteConfiguration,
+    AssistSatelliteWakeWord,
+)
 
+# pylint: disable-next=hass-component-root-import
path: tests/components/esphome/test_select.py
user: login: joostlek
body: why do we now need this pylint exception? the code in the next line didnt change right
created_at: 2025-04-24T19:37:00Z
author_association: MEMBER
diff_hunk: @@ -144,19 +146,21 @@ async def test_update_entity(
     # Compile success, upload fails
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
+            return_value=True,
         ) as mock_compile,
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
+            return_value=False,
         ) as mock_upload,
         pytest.raises(
             HomeAssistantError,
             match="OTA",
         ),
     ):
         await hass.services.async_call(
-            "update",
-            "install",
+            UPDATE_DOMAIN,
+            SERVICE_INSTALL,
             {"entity_id": "update.test_firmware"},
path: tests/components/esphome/test_update.py
user: login: joostlek
body: ```suggestion
            {ATTR_ENTITY_ID: "update.test_firmware"},
```
created_at: 2025-04-24T19:37:26Z
author_association: MEMBER
diff_hunk: @@ -1,9 +1,24 @@
 """Test ESPHome selects."""
 
+from collections.abc import Awaitable, Callable
 from unittest.mock import call
 
-from aioesphomeapi import APIClient, SelectInfo, SelectState
+from aioesphomeapi import (
+    APIClient,
+    EntityInfo,
+    EntityState,
+    SelectInfo,
+    SelectState,
+    UserService,
+    VoiceAssistantFeature,
+)
+
+from homeassistant.components.assist_satellite import (
+    AssistSatelliteConfiguration,
+    AssistSatelliteWakeWord,
+)
 
+# pylint: disable-next=hass-component-root-import
path: tests/components/esphome/test_select.py
user: login: bdraco
body: copied from the assist tests. Maybe not needed there either. Will check and remove if thats the case
created_at: 2025-04-24T19:39:14Z
author_association: MEMBER
diff_hunk: @@ -1,9 +1,24 @@
 """Test ESPHome selects."""
 
+from collections.abc import Awaitable, Callable
 from unittest.mock import call
 
-from aioesphomeapi import APIClient, SelectInfo, SelectState
+from aioesphomeapi import (
+    APIClient,
+    EntityInfo,
+    EntityState,
+    SelectInfo,
+    SelectState,
+    UserService,
+    VoiceAssistantFeature,
+)
+
+from homeassistant.components.assist_satellite import (
+    AssistSatelliteConfiguration,
+    AssistSatelliteWakeWord,
+)
 
+# pylint: disable-next=hass-component-root-import
path: tests/components/esphome/test_select.py
user: login: bdraco
body: Looks like its needed there but makes not sense here after ruff rearranged the imports. Removed
created_at: 2025-04-24T19:40:27Z
author_association: MEMBER
diff_hunk: @@ -1,9 +1,24 @@
 """Test ESPHome selects."""
 
+from collections.abc import Awaitable, Callable
 from unittest.mock import call
 
-from aioesphomeapi import APIClient, SelectInfo, SelectState
+from aioesphomeapi import (
+    APIClient,
+    EntityInfo,
+    EntityState,
+    SelectInfo,
+    SelectState,
+    UserService,
+    VoiceAssistantFeature,
+)
+
+from homeassistant.components.assist_satellite import (
+    AssistSatelliteConfiguration,
+    AssistSatelliteWakeWord,
+)
 
+# pylint: disable-next=hass-component-root-import
path: tests/components/esphome/test_select.py
user: login: bdraco
body: [esphome_quality_round_2 6fbdf27475c] remove # pylint: disable-next=hass-component-root-import
 1 file changed, 2 deletions(-)
created_at: 2025-04-24T19:40:38Z
author_association: MEMBER
diff_hunk: @@ -144,19 +146,21 @@ async def test_update_entity(
     # Compile success, upload fails
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
+            return_value=True,
         ) as mock_compile,
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
+            return_value=False,
         ) as mock_upload,
         pytest.raises(
             HomeAssistantError,
             match="OTA",
         ),
     ):
         await hass.services.async_call(
-            "update",
-            "install",
+            UPDATE_DOMAIN,
+            SERVICE_INSTALL,
             {"entity_id": "update.test_firmware"},
path: tests/components/esphome/test_update.py
user: login: bdraco
body: found a few more
created_at: 2025-04-24T19:42:45Z
author_association: MEMBER
diff_hunk: @@ -144,19 +146,21 @@ async def test_update_entity(
     # Compile success, upload fails
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
+            return_value=True,
         ) as mock_compile,
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
+            return_value=False,
         ) as mock_upload,
         pytest.raises(
             HomeAssistantError,
             match="OTA",
         ),
     ):
         await hass.services.async_call(
-            "update",
-            "install",
+            UPDATE_DOMAIN,
+            SERVICE_INSTALL,
             {"entity_id": "update.test_firmware"},
path: tests/components/esphome/test_update.py
user: login: bdraco
body: [esphome_quality_round_2 14e359a27ae] ATTR_ENTITY_ID
 3 files changed, 12 insertions(+), 12 deletions(-)
created_at: 2025-04-24T19:43:18Z
author_association: MEMBER
diff_hunk: @@ -144,19 +146,21 @@ async def test_update_entity(
     # Compile success, upload fails
     with (
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.compile",
+            return_value=True,
         ) as mock_compile,
         patch(
-            "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False
+            "homeassistant.components.esphome.coordinator.ESPHomeDashboardAPI.upload",
+            return_value=False,
         ) as mock_upload,
         pytest.raises(
             HomeAssistantError,
             match="OTA",
         ),
     ):
         await hass.services.async_call(
-            "update",
-            "install",
+            UPDATE_DOMAIN,
+            SERVICE_INSTALL,
             {"entity_id": "update.test_firmware"},
path: tests/components/esphome/test_update.py
user: login: bdraco
body: except one is not a service call so reverted that in 1042390704c and 614f381633f
created_at: 2025-04-24T19:44:35Z
author_association: MEMBER

PR comments:
user: login: home-assistant[bot]
created_at: 2025-04-24T19:28:55Z
body: 
Hey there @ottowinter, @jesserockz, @kbx81, mind taking a look at this pull request as it has been labeled with an integration (`esphome`) you are listed as a [code owner](https://github.com/home-assistant/core/blob/dev/CODEOWNERS#L446) for? Thanks!

<details>
<summary>Code owner commands</summary>


Code owners of `esphome` can trigger bot actions by commenting:

- `@home-assistant close` Closes the pull request.
- `@home-assistant rename Awesome new title` Renames the pull request.
- `@home-assistant reopen` Reopen the pull request.
- `@home-assistant unassign esphome` Removes the current integration label and assignees on the pull request, add the integration domain after the command.
- `@home-assistant add-label needs-more-information` Add a label (needs-more-information, problem in dependency, problem in custom component) to the pull request.
- `@home-assistant remove-label needs-more-information` Remove a label (needs-more-information, problem in dependency, problem in custom component) on the pull request.

</details>


author_association: CONTRIBUTOR
user: login: bdraco
created_at: 2025-04-24T19:51:30Z
body: Thanks
author_association: MEMBER