mirror of
https://github.com/home-assistant/core.git
synced 2026-04-18 15:39:12 +02:00
Add initial support for PlayerOptions: Number entities to Music Assistant (#162669)
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
This commit is contained in:
@@ -49,7 +49,11 @@ if TYPE_CHECKING:
|
||||
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER]
|
||||
PLATFORMS = [
|
||||
Platform.BUTTON,
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.NUMBER,
|
||||
]
|
||||
|
||||
CONNECT_TIMEOUT = 10
|
||||
LISTEN_READY_TIMEOUT = 30
|
||||
|
||||
@@ -80,3 +80,5 @@ ATTR_FANART_IMAGE = "fanart_image"
|
||||
ATTR_CONF_EXPOSE_PLAYER_TO_HA = "expose_player_to_ha"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX = "player_options."
|
||||
|
||||
@@ -6,8 +6,9 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from music_assistant_models.enums import EventType
|
||||
from music_assistant_models.event import MassEvent
|
||||
from music_assistant_models.player import Player
|
||||
from music_assistant_models.player import Player, PlayerOption
|
||||
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
@@ -84,3 +85,45 @@ class MusicAssistantEntity(Entity):
|
||||
|
||||
async def async_on_update(self) -> None:
|
||||
"""Handle player updates."""
|
||||
|
||||
|
||||
class MusicAssistantPlayerOptionEntity(MusicAssistantEntity):
|
||||
"""Base entity for Music Assistant Player Options."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
def __init__(
|
||||
self, mass: MusicAssistantClient, player_id: str, player_option: PlayerOption
|
||||
) -> None:
|
||||
"""Initialize MusicAssistantPlayerOptionEntity."""
|
||||
super().__init__(mass, player_id)
|
||||
|
||||
self.mass_option_key = player_option.key
|
||||
self.mass_type = player_option.type
|
||||
|
||||
self.on_player_option_update(player_option)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
# need callbacks of parent to catch availability
|
||||
await super().async_added_to_hass()
|
||||
|
||||
# main callback for player options
|
||||
self.async_on_remove(
|
||||
self.mass.subscribe(
|
||||
self.__on_mass_player_options_update,
|
||||
EventType.PLAYER_OPTIONS_UPDATED,
|
||||
self.player_id,
|
||||
)
|
||||
)
|
||||
|
||||
def __on_mass_player_options_update(self, event: MassEvent) -> None:
|
||||
"""Call when we receive an event from MusicAssistant."""
|
||||
for option in self.player.options:
|
||||
if option.key == self.mass_option_key:
|
||||
self.on_player_option_update(option)
|
||||
self.async_write_ha_state()
|
||||
break
|
||||
|
||||
def on_player_option_update(self, player_option: PlayerOption) -> None:
|
||||
"""Callback for player option updates."""
|
||||
|
||||
127
homeassistant/components/music_assistant/number.py
Normal file
127
homeassistant/components/music_assistant/number.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""Music Assistant Number platform."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from music_assistant_client.client import MusicAssistantClient
|
||||
from music_assistant_models.player import PlayerOption, PlayerOptionType
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import MusicAssistantConfigEntry
|
||||
from .const import PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX
|
||||
from .entity import MusicAssistantPlayerOptionEntity
|
||||
from .helpers import catch_musicassistant_error
|
||||
|
||||
PLAYER_OPTIONS_TRANSLATION_KEYS_NUMBER: Final[list[str]] = [
|
||||
"bass",
|
||||
"dialogue_level",
|
||||
"dialogue_lift",
|
||||
"dts_dialogue_control",
|
||||
"equalizer_high",
|
||||
"equalizer_low",
|
||||
"equalizer_mid",
|
||||
"subwoofer_volume",
|
||||
"treble",
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: MusicAssistantConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Music Assistant Number Entities (Player Options) from Config Entry."""
|
||||
mass = entry.runtime_data.mass
|
||||
|
||||
def add_player(player_id: str) -> None:
|
||||
"""Handle add player."""
|
||||
player = mass.players.get(player_id)
|
||||
if player is None:
|
||||
return
|
||||
entities: list[MusicAssistantPlayerConfigNumber] = []
|
||||
for player_option in player.options:
|
||||
if (
|
||||
not player_option.read_only
|
||||
and player_option.type
|
||||
in (
|
||||
PlayerOptionType.INTEGER,
|
||||
PlayerOptionType.FLOAT,
|
||||
)
|
||||
and not player_option.options # these we map to select
|
||||
):
|
||||
# the MA translation key must have the format player_options.<translation key>
|
||||
# we ignore entities with unknown translation keys.
|
||||
if (
|
||||
player_option.translation_key is None
|
||||
or not player_option.translation_key.startswith(
|
||||
PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX
|
||||
)
|
||||
):
|
||||
continue
|
||||
translation_key = player_option.translation_key[
|
||||
len(PLAYER_OPTIONS_TRANSLATION_KEY_PREFIX) :
|
||||
]
|
||||
if translation_key not in PLAYER_OPTIONS_TRANSLATION_KEYS_NUMBER:
|
||||
continue
|
||||
|
||||
entities.append(
|
||||
MusicAssistantPlayerConfigNumber(
|
||||
mass,
|
||||
player_id,
|
||||
player_option=player_option,
|
||||
entity_description=NumberEntityDescription(
|
||||
key=player_option.key,
|
||||
translation_key=translation_key,
|
||||
),
|
||||
)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
# register callback to add players when they are discovered
|
||||
entry.runtime_data.platform_handlers.setdefault(Platform.NUMBER, add_player)
|
||||
|
||||
|
||||
class MusicAssistantPlayerConfigNumber(MusicAssistantPlayerOptionEntity, NumberEntity):
|
||||
"""Representation of a Number entity to control player provider dependent settings."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mass: MusicAssistantClient,
|
||||
player_id: str,
|
||||
player_option: PlayerOption,
|
||||
entity_description: NumberEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize MusicAssistantPlayerConfigNumber."""
|
||||
super().__init__(mass, player_id, player_option)
|
||||
|
||||
self.entity_description = entity_description
|
||||
|
||||
@catch_musicassistant_error
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set a new value."""
|
||||
_value = round(value) if self.mass_type == PlayerOptionType.INTEGER else value
|
||||
await self.mass.players.set_option(
|
||||
self.player_id,
|
||||
self.mass_option_key,
|
||||
_value,
|
||||
)
|
||||
|
||||
def on_player_option_update(self, player_option: PlayerOption) -> None:
|
||||
"""Update on player option update."""
|
||||
if player_option.min_value is not None:
|
||||
self._attr_native_min_value = player_option.min_value
|
||||
if player_option.max_value is not None:
|
||||
self._attr_native_max_value = player_option.max_value
|
||||
if player_option.step is not None:
|
||||
self._attr_native_step = player_option.step
|
||||
|
||||
self._attr_native_value = (
|
||||
player_option.value
|
||||
if isinstance(player_option.value, (int, float))
|
||||
else None
|
||||
)
|
||||
@@ -53,6 +53,35 @@
|
||||
"favorite_now_playing": {
|
||||
"name": "Favorite current song"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"bass": {
|
||||
"name": "Bass"
|
||||
},
|
||||
"dialogue_level": {
|
||||
"name": "Dialogue level"
|
||||
},
|
||||
"dialogue_lift": {
|
||||
"name": "Dialogue lift"
|
||||
},
|
||||
"dts_dialogue_control": {
|
||||
"name": "DTS dialogue control"
|
||||
},
|
||||
"equalizer_high": {
|
||||
"name": "Equalizer high"
|
||||
},
|
||||
"equalizer_low": {
|
||||
"name": "Equalizer low"
|
||||
},
|
||||
"equalizer_mid": {
|
||||
"name": "Equalizer mid"
|
||||
},
|
||||
"subwoofer_volume": {
|
||||
"name": "Subwoofer volume"
|
||||
},
|
||||
"treble": {
|
||||
"name": "Treble"
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -26,6 +26,131 @@
|
||||
"volume_level": 20,
|
||||
"volume_muted": false,
|
||||
"group_members": [],
|
||||
"options": [
|
||||
{
|
||||
"key": "treble",
|
||||
"name": "Treble",
|
||||
"type": "integer",
|
||||
"translation_key": "player_options.treble",
|
||||
"translation_params": null,
|
||||
"value": -6,
|
||||
"read_only": false,
|
||||
"min_value": -10,
|
||||
"max_value": 10,
|
||||
"step": 1,
|
||||
"options": null
|
||||
},
|
||||
{
|
||||
"key": "bass",
|
||||
"name": "Bass",
|
||||
"type": "float",
|
||||
"translation_key": "player_options.bass",
|
||||
"translation_params": null,
|
||||
"value": -6.0,
|
||||
"read_only": false,
|
||||
"min_value": -10.0,
|
||||
"max_value": 10.0,
|
||||
"step": 1.0,
|
||||
"options": null
|
||||
},
|
||||
{
|
||||
"key": "treble_ro",
|
||||
"name": "Treble RO",
|
||||
"type": "integer",
|
||||
"translation_key": "player_options.treble",
|
||||
"translation_params": null,
|
||||
"value": -6,
|
||||
"read_only": true,
|
||||
"min_value": null,
|
||||
"max_value": null,
|
||||
"step": null,
|
||||
"options": null
|
||||
},
|
||||
{
|
||||
"key": "enhancer",
|
||||
"name": "Enhancer",
|
||||
"type": "boolean",
|
||||
"translation_key": "player_options.enhancer",
|
||||
"translation_params": null,
|
||||
"value": false,
|
||||
"read_only": false,
|
||||
"min_value": null,
|
||||
"max_value": null,
|
||||
"step": null,
|
||||
"options": null
|
||||
},
|
||||
{
|
||||
"key": "enhancer_ro",
|
||||
"name": "Enhancer RO",
|
||||
"type": "boolean",
|
||||
"translation_key": "player_options.enhancer",
|
||||
"translation_params": null,
|
||||
"value": false,
|
||||
"read_only": true,
|
||||
"min_value": null,
|
||||
"max_value": null,
|
||||
"step": null,
|
||||
"options": null
|
||||
},
|
||||
{
|
||||
"key": "network_name",
|
||||
"name": "Network Name",
|
||||
"type": "string",
|
||||
"translation_key": "player_options.network_name",
|
||||
"translation_params": null,
|
||||
"value": "receiver",
|
||||
"read_only": false,
|
||||
"min_value": null,
|
||||
"max_value": null,
|
||||
"step": null,
|
||||
"options": null
|
||||
},
|
||||
{
|
||||
"key": "network_name_ro",
|
||||
"name": "Network Name RO",
|
||||
"type": "string",
|
||||
"translation_key": "player_options.network_name",
|
||||
"translation_params": null,
|
||||
"value": "receiver ro",
|
||||
"read_only": true,
|
||||
"min_value": null,
|
||||
"max_value": null,
|
||||
"step": null,
|
||||
"options": null
|
||||
},
|
||||
{
|
||||
"key": "link_audio_delay",
|
||||
"name": "Link Audio Delay",
|
||||
"type": "string",
|
||||
"translation_key": "player_options.link_audio_delay",
|
||||
"translation_params": null,
|
||||
"value": "lip_sync",
|
||||
"read_only": false,
|
||||
"min_value": null,
|
||||
"max_value": null,
|
||||
"step": null,
|
||||
"options": [
|
||||
{
|
||||
"key": "audio_sync",
|
||||
"name": "audio_sync",
|
||||
"type": "string",
|
||||
"value": "audio_sync"
|
||||
},
|
||||
{
|
||||
"key": "balanced",
|
||||
"name": "balanced",
|
||||
"type": "string",
|
||||
"value": "balanced"
|
||||
},
|
||||
{
|
||||
"key": "lip_sync",
|
||||
"name": "lip_sync",
|
||||
"type": "string",
|
||||
"value": "lip_sync"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"active_source": "00:00:00:00:00:01",
|
||||
"active_group": null,
|
||||
"current_media": null,
|
||||
|
||||
119
tests/components/music_assistant/snapshots/test_number.ambr
Normal file
119
tests/components/music_assistant/snapshots/test_number.ambr
Normal file
@@ -0,0 +1,119 @@
|
||||
# serializer version: 1
|
||||
# name: test_number_entities[number.test_player_1_bass-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 10.0,
|
||||
'min': -10.0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.test_player_1_bass',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Bass',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Bass',
|
||||
'platform': 'music_assistant',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'bass',
|
||||
'unique_id': '00:00:00:00:00:01_bass',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_number_entities[number.test_player_1_bass-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Test Player 1 Bass',
|
||||
'max': 10.0,
|
||||
'min': -10.0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_player_1_bass',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '-6.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_number_entities[number.test_player_1_treble-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 10,
|
||||
'min': -10,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.test_player_1_treble',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Treble',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Treble',
|
||||
'platform': 'music_assistant',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'treble',
|
||||
'unique_id': '00:00:00:00:00:01_treble',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_number_entities[number.test_player_1_treble-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Test Player 1 Treble',
|
||||
'max': 10,
|
||||
'min': -10,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_player_1_treble',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '-6',
|
||||
})
|
||||
# ---
|
||||
153
tests/components/music_assistant/test_number.py
Normal file
153
tests/components/music_assistant/test_number.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""Test Music Assistant number entities."""
|
||||
|
||||
from unittest.mock import MagicMock, call
|
||||
|
||||
from music_assistant_models.enums import EventType
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.music_assistant.const import DOMAIN
|
||||
from homeassistant.components.music_assistant.number import (
|
||||
PLAYER_OPTIONS_TRANSLATION_KEYS_NUMBER,
|
||||
)
|
||||
from homeassistant.components.number import (
|
||||
ATTR_VALUE,
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.translation import LOCALE_EN, async_get_translations
|
||||
|
||||
from .common import (
|
||||
setup_integration_from_fixtures,
|
||||
snapshot_music_assistant_entities,
|
||||
trigger_subscription_callback,
|
||||
)
|
||||
|
||||
|
||||
async def test_number_entities(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
music_assistant_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test number entities."""
|
||||
await setup_integration_from_fixtures(hass, music_assistant_client)
|
||||
snapshot_music_assistant_entities(hass, entity_registry, snapshot, Platform.NUMBER)
|
||||
|
||||
|
||||
async def test_number_set_action(
|
||||
hass: HomeAssistant,
|
||||
music_assistant_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test number set action."""
|
||||
mass_player_id = "00:00:00:00:00:01"
|
||||
mass_option_key = "treble"
|
||||
entity_id = "number.test_player_1_treble"
|
||||
|
||||
option_value = 3
|
||||
|
||||
await setup_integration_from_fixtures(hass, music_assistant_client)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
|
||||
# test within range
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_VALUE: option_value,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert music_assistant_client.send_command.call_count == 1
|
||||
assert music_assistant_client.send_command.call_args == call(
|
||||
"players/cmd/set_option",
|
||||
player_id=mass_player_id,
|
||||
option_key=mass_option_key,
|
||||
option_value=option_value,
|
||||
)
|
||||
|
||||
# test out of range
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_VALUE: 20,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_external_update(
|
||||
hass: HomeAssistant,
|
||||
music_assistant_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test external value update."""
|
||||
mass_player_id = "00:00:00:00:00:01"
|
||||
mass_option_key = "treble"
|
||||
entity_id = "number.test_player_1_treble"
|
||||
|
||||
await setup_integration_from_fixtures(hass, music_assistant_client)
|
||||
|
||||
# get current option and remove it
|
||||
number_option = next(
|
||||
option
|
||||
for option in music_assistant_client.players._players[mass_player_id].options
|
||||
if option.key == mass_option_key
|
||||
)
|
||||
music_assistant_client.players._players[mass_player_id].options.remove(
|
||||
number_option
|
||||
)
|
||||
|
||||
# set new value different from previous one
|
||||
previous_value = number_option.value
|
||||
new_value = 5
|
||||
number_option.value = new_value
|
||||
assert previous_value != number_option.value
|
||||
music_assistant_client.players._players[mass_player_id].options.append(
|
||||
number_option
|
||||
)
|
||||
|
||||
await trigger_subscription_callback(
|
||||
hass, music_assistant_client, EventType.PLAYER_OPTIONS_UPDATED, mass_player_id
|
||||
)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert int(float(state.state)) == new_value
|
||||
|
||||
|
||||
async def test_ignored(
|
||||
hass: HomeAssistant,
|
||||
music_assistant_client: MagicMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test that non-compatible player options are ignored."""
|
||||
config_entry = await setup_integration_from_fixtures(hass, music_assistant_client)
|
||||
registry_entries = er.async_entries_for_config_entry(
|
||||
entity_registry, config_entry_id=config_entry.entry_id
|
||||
)
|
||||
# we only have two non read-only player options, bass and treble
|
||||
assert sum(1 for entry in registry_entries if entry.domain == NUMBER_DOMAIN) == 2
|
||||
|
||||
|
||||
async def test_name_translation_availability(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Verify, that the list of available translation keys is reflected in strings.json."""
|
||||
# verify, that PLAYER_OPTIONS_TRANSLATION_KEYS_NUMBER matches strings.json
|
||||
translations = await async_get_translations(
|
||||
hass, language=LOCALE_EN, category="entity", integrations=[DOMAIN]
|
||||
)
|
||||
prefix = f"component.{DOMAIN}.entity.{Platform.NUMBER.value}."
|
||||
for translation_key in PLAYER_OPTIONS_TRANSLATION_KEYS_NUMBER:
|
||||
assert translations.get(f"{prefix}{translation_key}.name") is not None, (
|
||||
f"{translation_key} is missing in strings.json for platform number"
|
||||
)
|
||||
Reference in New Issue
Block a user