mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
Add button entity to Music Assistant to add currently playing item to favorites (#145626)
* Add action to Music Assistant to add currently playing item to favorites * add test * Convert to button entity * review comments * Update test_button.ambr * Fix --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
parent
e494f66c02
commit
673a2e35ad
@ -3,7 +3,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from music_assistant_client import MusicAssistantClient
|
from music_assistant_client import MusicAssistantClient
|
||||||
@ -31,7 +32,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER]
|
||||||
|
|
||||||
CONNECT_TIMEOUT = 10
|
CONNECT_TIMEOUT = 10
|
||||||
LISTEN_READY_TIMEOUT = 30
|
LISTEN_READY_TIMEOUT = 30
|
||||||
@ -39,6 +40,7 @@ LISTEN_READY_TIMEOUT = 30
|
|||||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
type MusicAssistantConfigEntry = ConfigEntry[MusicAssistantEntryData]
|
type MusicAssistantConfigEntry = ConfigEntry[MusicAssistantEntryData]
|
||||||
|
type PlayerAddCallback = Callable[[str], None]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -47,6 +49,8 @@ class MusicAssistantEntryData:
|
|||||||
|
|
||||||
mass: MusicAssistantClient
|
mass: MusicAssistantClient
|
||||||
listen_task: asyncio.Task
|
listen_task: asyncio.Task
|
||||||
|
discovered_players: set[str] = field(default_factory=set)
|
||||||
|
platform_handlers: dict[Platform, PlayerAddCallback] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
@ -122,6 +126,33 @@ async def async_setup_entry(
|
|||||||
# initialize platforms
|
# initialize platforms
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
# register listener for new players
|
||||||
|
async def handle_player_added(event: MassEvent) -> None:
|
||||||
|
"""Handle Mass Player Added event."""
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert event.object_id is not None
|
||||||
|
if event.object_id in entry.runtime_data.discovered_players:
|
||||||
|
return
|
||||||
|
player = mass.players.get(event.object_id)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert player is not None
|
||||||
|
if not player.expose_to_ha:
|
||||||
|
return
|
||||||
|
entry.runtime_data.discovered_players.add(event.object_id)
|
||||||
|
# run callback for each platform
|
||||||
|
for callback in entry.runtime_data.platform_handlers.values():
|
||||||
|
callback(event.object_id)
|
||||||
|
|
||||||
|
entry.async_on_unload(mass.subscribe(handle_player_added, EventType.PLAYER_ADDED))
|
||||||
|
|
||||||
|
# add all current players
|
||||||
|
for player in mass.players:
|
||||||
|
if not player.expose_to_ha:
|
||||||
|
continue
|
||||||
|
entry.runtime_data.discovered_players.add(player.player_id)
|
||||||
|
for callback in entry.runtime_data.platform_handlers.values():
|
||||||
|
callback(player.player_id)
|
||||||
|
|
||||||
# register listener for removed players
|
# register listener for removed players
|
||||||
async def handle_player_removed(event: MassEvent) -> None:
|
async def handle_player_removed(event: MassEvent) -> None:
|
||||||
"""Handle Mass Player Removed event."""
|
"""Handle Mass Player Removed event."""
|
||||||
|
53
homeassistant/components/music_assistant/button.py
Normal file
53
homeassistant/components/music_assistant/button.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""Music Assistant Button platform."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
|
from . import MusicAssistantConfigEntry
|
||||||
|
from .entity import MusicAssistantEntity
|
||||||
|
from .helpers import catch_musicassistant_error
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: MusicAssistantConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Music Assistant MediaPlayer(s) from Config Entry."""
|
||||||
|
mass = entry.runtime_data.mass
|
||||||
|
|
||||||
|
def add_player(player_id: str) -> None:
|
||||||
|
"""Handle add player."""
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
# Add button entity to favorite the currently playing item on the player
|
||||||
|
MusicAssistantFavoriteButton(mass, player_id)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# register callback to add players when they are discovered
|
||||||
|
entry.runtime_data.platform_handlers.setdefault(Platform.BUTTON, add_player)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicAssistantFavoriteButton(MusicAssistantEntity, ButtonEntity):
|
||||||
|
"""Representation of a Button entity to favorite the currently playing item on a player."""
|
||||||
|
|
||||||
|
entity_description = ButtonEntityDescription(
|
||||||
|
key="favorite_now_playing",
|
||||||
|
translation_key="favorite_now_playing",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return availability of entity."""
|
||||||
|
# mark the button as unavailable if the player has no current media item
|
||||||
|
return super().available and self.player.current_media is not None
|
||||||
|
|
||||||
|
@catch_musicassistant_error
|
||||||
|
async def async_press(self) -> None:
|
||||||
|
"""Handle the button press command."""
|
||||||
|
await self.mass.players.add_currently_playing_to_favorites(self.player_id)
|
28
homeassistant/components/music_assistant/helpers.py
Normal file
28
homeassistant/components/music_assistant/helpers.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""Helpers for the Music Assistant integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable, Coroutine
|
||||||
|
import functools
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from music_assistant_models.errors import MusicAssistantError
|
||||||
|
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
|
|
||||||
|
def catch_musicassistant_error[**_P, _R](
|
||||||
|
func: Callable[_P, Coroutine[Any, Any, _R]],
|
||||||
|
) -> Callable[_P, Coroutine[Any, Any, _R]]:
|
||||||
|
"""Check and convert commands to players."""
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
|
||||||
|
"""Catch Music Assistant errors and convert to Home Assistant error."""
|
||||||
|
try:
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
except MusicAssistantError as err:
|
||||||
|
error_msg = str(err) or err.__class__.__name__
|
||||||
|
raise HomeAssistantError(error_msg) from err
|
||||||
|
|
||||||
|
return wrapper
|
@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"entity": {
|
||||||
|
"button": {
|
||||||
|
"favorite_now_playing": {
|
||||||
|
"default": "mdi:heart-plus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"play_media": { "service": "mdi:play" },
|
"play_media": { "service": "mdi:play" },
|
||||||
"play_announcement": { "service": "mdi:bullhorn" },
|
"play_announcement": { "service": "mdi:bullhorn" },
|
||||||
|
@ -3,11 +3,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable, Coroutine, Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import functools
|
|
||||||
import os
|
import os
|
||||||
from typing import TYPE_CHECKING, Any, Concatenate
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from music_assistant_models.constants import PLAYER_CONTROL_NONE
|
from music_assistant_models.constants import PLAYER_CONTROL_NONE
|
||||||
from music_assistant_models.enums import (
|
from music_assistant_models.enums import (
|
||||||
@ -18,7 +17,7 @@ from music_assistant_models.enums import (
|
|||||||
QueueOption,
|
QueueOption,
|
||||||
RepeatMode as MassRepeatMode,
|
RepeatMode as MassRepeatMode,
|
||||||
)
|
)
|
||||||
from music_assistant_models.errors import MediaNotFoundError, MusicAssistantError
|
from music_assistant_models.errors import MediaNotFoundError
|
||||||
from music_assistant_models.event import MassEvent
|
from music_assistant_models.event import MassEvent
|
||||||
from music_assistant_models.media_items import ItemMapping, MediaItemType, Track
|
from music_assistant_models.media_items import ItemMapping, MediaItemType, Track
|
||||||
from music_assistant_models.player_queue import PlayerQueue
|
from music_assistant_models.player_queue import PlayerQueue
|
||||||
@ -40,7 +39,7 @@ from homeassistant.components.media_player import (
|
|||||||
SearchMediaQuery,
|
SearchMediaQuery,
|
||||||
async_process_play_media_url,
|
async_process_play_media_url,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_NAME, STATE_OFF
|
from homeassistant.const import ATTR_NAME, STATE_OFF, Platform
|
||||||
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
|
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
|
||||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||||
@ -76,6 +75,7 @@ from .const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from .entity import MusicAssistantEntity
|
from .entity import MusicAssistantEntity
|
||||||
|
from .helpers import catch_musicassistant_error
|
||||||
from .media_browser import async_browse_media, async_search_media
|
from .media_browser import async_browse_media, async_search_media
|
||||||
from .schemas import QUEUE_DETAILS_SCHEMA, queue_item_dict_from_mass_item
|
from .schemas import QUEUE_DETAILS_SCHEMA, queue_item_dict_from_mass_item
|
||||||
|
|
||||||
@ -120,25 +120,6 @@ SERVICE_TRANSFER_QUEUE = "transfer_queue"
|
|||||||
SERVICE_GET_QUEUE = "get_queue"
|
SERVICE_GET_QUEUE = "get_queue"
|
||||||
|
|
||||||
|
|
||||||
def catch_musicassistant_error[_R, **P](
|
|
||||||
func: Callable[Concatenate[MusicAssistantPlayer, P], Coroutine[Any, Any, _R]],
|
|
||||||
) -> Callable[Concatenate[MusicAssistantPlayer, P], Coroutine[Any, Any, _R]]:
|
|
||||||
"""Check and log commands to players."""
|
|
||||||
|
|
||||||
@functools.wraps(func)
|
|
||||||
async def wrapper(
|
|
||||||
self: MusicAssistantPlayer, *args: P.args, **kwargs: P.kwargs
|
|
||||||
) -> _R:
|
|
||||||
"""Catch Music Assistant errors and convert to Home Assistant error."""
|
|
||||||
try:
|
|
||||||
return await func(self, *args, **kwargs)
|
|
||||||
except MusicAssistantError as err:
|
|
||||||
error_msg = str(err) or err.__class__.__name__
|
|
||||||
raise HomeAssistantError(error_msg) from err
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: MusicAssistantConfigEntry,
|
entry: MusicAssistantConfigEntry,
|
||||||
@ -146,33 +127,13 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Music Assistant MediaPlayer(s) from Config Entry."""
|
"""Set up Music Assistant MediaPlayer(s) from Config Entry."""
|
||||||
mass = entry.runtime_data.mass
|
mass = entry.runtime_data.mass
|
||||||
added_ids = set()
|
|
||||||
|
|
||||||
async def handle_player_added(event: MassEvent) -> None:
|
def add_player(player_id: str) -> None:
|
||||||
"""Handle Mass Player Added event."""
|
"""Handle add player."""
|
||||||
if TYPE_CHECKING:
|
async_add_entities([MusicAssistantPlayer(mass, player_id)])
|
||||||
assert event.object_id is not None
|
|
||||||
if event.object_id in added_ids:
|
|
||||||
return
|
|
||||||
player = mass.players.get(event.object_id)
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
assert player is not None
|
|
||||||
if not player.expose_to_ha:
|
|
||||||
return
|
|
||||||
added_ids.add(event.object_id)
|
|
||||||
async_add_entities([MusicAssistantPlayer(mass, event.object_id)])
|
|
||||||
|
|
||||||
# register listener for new players
|
# register callback to add players when they are discovered
|
||||||
entry.async_on_unload(mass.subscribe(handle_player_added, EventType.PLAYER_ADDED))
|
entry.runtime_data.platform_handlers.setdefault(Platform.MEDIA_PLAYER, add_player)
|
||||||
mass_players = []
|
|
||||||
# add all current players
|
|
||||||
for player in mass.players:
|
|
||||||
if not player.expose_to_ha:
|
|
||||||
continue
|
|
||||||
added_ids.add(player.player_id)
|
|
||||||
mass_players.append(MusicAssistantPlayer(mass, player.player_id))
|
|
||||||
|
|
||||||
async_add_entities(mass_players)
|
|
||||||
|
|
||||||
# add platform service for play_media with advanced options
|
# add platform service for play_media with advanced options
|
||||||
platform = async_get_current_platform()
|
platform = async_get_current_platform()
|
||||||
|
@ -31,6 +31,13 @@
|
|||||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"entity": {
|
||||||
|
"button": {
|
||||||
|
"favorite_now_playing": {
|
||||||
|
"name": "Favorite current song"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"issues": {
|
"issues": {
|
||||||
"invalid_server_version": {
|
"invalid_server_version": {
|
||||||
"title": "The Music Assistant server is not the correct version",
|
"title": "The Music Assistant server is not the correct version",
|
||||||
|
145
tests/components/music_assistant/snapshots/test_button.ambr
Normal file
145
tests/components/music_assistant/snapshots/test_button.ambr
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_button_entities[button.my_super_test_player_2_favorite_current_song-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'button',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'button.my_super_test_player_2_favorite_current_song',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Favorite current song',
|
||||||
|
'platform': 'music_assistant',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'favorite_now_playing',
|
||||||
|
'unique_id': '00:00:00:00:00:02_favorite_now_playing',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_button_entities[button.my_super_test_player_2_favorite_current_song-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'My Super Test Player 2 Favorite current song',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'button.my_super_test_player_2_favorite_current_song',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_button_entities[button.test_group_player_1_favorite_current_song-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'button',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'button.test_group_player_1_favorite_current_song',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Favorite current song',
|
||||||
|
'platform': 'music_assistant',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'favorite_now_playing',
|
||||||
|
'unique_id': 'test_group_player_1_favorite_now_playing',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_button_entities[button.test_group_player_1_favorite_current_song-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Test Group Player 1 Favorite current song',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'button.test_group_player_1_favorite_current_song',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_button_entities[button.test_player_1_favorite_current_song-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'button',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'button.test_player_1_favorite_current_song',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Favorite current song',
|
||||||
|
'platform': 'music_assistant',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'favorite_now_playing',
|
||||||
|
'unique_id': '00:00:00:00:00:01_favorite_now_playing',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_button_entities[button.test_player_1_favorite_current_song-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Test Player 1 Favorite current song',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'button.test_player_1_favorite_current_song',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unavailable',
|
||||||
|
})
|
||||||
|
# ---
|
48
tests/components/music_assistant/test_button.py
Normal file
48
tests/components/music_assistant/test_button.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"""Test Music Assistant button entities."""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock, call
|
||||||
|
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from .common import setup_integration_from_fixtures, snapshot_music_assistant_entities
|
||||||
|
|
||||||
|
|
||||||
|
async def test_button_entities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
music_assistant_client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test media player."""
|
||||||
|
await setup_integration_from_fixtures(hass, music_assistant_client)
|
||||||
|
snapshot_music_assistant_entities(hass, entity_registry, snapshot, Platform.BUTTON)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_button_press_action(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
music_assistant_client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test button press action."""
|
||||||
|
await setup_integration_from_fixtures(hass, music_assistant_client)
|
||||||
|
entity_id = "button.my_super_test_player_2_favorite_current_song"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
await hass.services.async_call(
|
||||||
|
BUTTON_DOMAIN,
|
||||||
|
SERVICE_PRESS,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert music_assistant_client.send_command.call_count == 1
|
||||||
|
assert music_assistant_client.send_command.call_args == call(
|
||||||
|
"music/favorites/add_item",
|
||||||
|
item="spotify://track/5d95dc5be77e4f7eb4939f62cfef527b",
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user