diff --git a/homeassistant/components/squeezebox/browse_media.py b/homeassistant/components/squeezebox/browse_media.py index b42a176f2280..3da966f2b1dd 100644 --- a/homeassistant/components/squeezebox/browse_media.py +++ b/homeassistant/components/squeezebox/browse_media.py @@ -47,6 +47,7 @@ MEDIA_TYPE_TO_SQUEEZEBOX: dict[str | MediaType, str] = { "new music": "new music", "album artists": "album artists", "artist tracks": "titles", + "genre tracks": "titles", MediaType.ALBUM: "album", MediaType.ARTIST: "artist", MediaType.TRACK: "title", @@ -63,6 +64,7 @@ SQUEEZEBOX_ID_BY_TYPE: dict[str | MediaType, str] = { MediaType.ARTIST: "artist_id", "artists": "artist_id", "artist tracks": "artist_id", + "genre tracks": "genre_id", MediaType.TRACK: "track_id", "tracks": "track_id", MediaType.PLAYLIST: "playlist_id", @@ -90,6 +92,7 @@ CONTENT_TYPE_MEDIA_CLASS: dict[str | MediaType, dict[str, MediaClass | str]] = { "new music": {"item": MediaClass.DIRECTORY, "children": MediaClass.ALBUM}, "album artists": {"item": MediaClass.DIRECTORY, "children": MediaClass.ARTIST}, "artist tracks": {"item": MediaClass.DIRECTORY, "children": MediaClass.TRACK}, + "genre tracks": {"item": MediaClass.DIRECTORY, "children": MediaClass.TRACK}, MediaType.ALBUM: {"item": MediaClass.ALBUM, "children": MediaClass.TRACK}, MediaType.ARTIST: {"item": MediaClass.ARTIST, "children": MediaClass.ALBUM}, MediaType.TRACK: {"item": MediaClass.TRACK, "children": ""}, @@ -117,6 +120,7 @@ CONTENT_TYPE_TO_CHILD_TYPE: dict[ "new music": MediaType.ALBUM, "album artists": MediaType.ARTIST, "artist tracks": MediaType.TRACK, + "genre tracks": MediaType.TRACK, MediaType.APPS: MediaType.APP, MediaType.APP: MediaType.TRACK, "favorite": None, diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index 0e7250d4678b..69ea433bd6a5 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -665,7 +665,8 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity): _valid_type_list = [ key for key in self._browse_data.content_type_media_class - if key not in ["apps", "app", "radios", "radio", "artist tracks"] + if key + not in ["apps", "app", "radios", "radio", "artist tracks", "genre tracks"] ] _media_content_type_list = ( @@ -687,9 +688,13 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity): }, ) - if query.media_content_id and MediaType.ARTIST in _media_content_type_list: - # LMS matches the albums of an artist by album title only - _media_content_type_list.append("artist tracks") + if query.media_content_id: + # Without full text search, LMS matches only album titles inside an + # artist and only artist names inside a genre + if MediaType.ARTIST in _media_content_type_list: + _media_content_type_list.append("artist tracks") + if MediaType.GENRE in _media_content_type_list: + _media_content_type_list.append("genre tracks") search_response_list: list[BrowseMedia] = [] diff --git a/tests/components/squeezebox/test_media_browser.py b/tests/components/squeezebox/test_media_browser.py index cdca9c77594c..891206eef7c0 100644 --- a/tests/components/squeezebox/test_media_browser.py +++ b/tests/components/squeezebox/test_media_browser.py @@ -260,12 +260,35 @@ async def test_async_search_media( assert category_level[0]["title"] == "Fake Item 1" -async def test_async_search_media_in_artist( +@pytest.mark.parametrize( + ("media_content_type", "category", "id_key", "first_item"), + [ + pytest.param( + MediaType.ARTIST, + "artist", + "artist_id", + ("Love Album", MediaClass.ALBUM, MediaType.ALBUM, "1", True), + id="artist", + ), + pytest.param( + MediaType.GENRE, + "genre", + "genre_id", + ("Love Band", MediaClass.ARTIST, MediaType.ARTIST, "1", True), + id="genre", + ), + ], +) +async def test_async_search_media_in_container( hass: HomeAssistant, lms: MagicMock, hass_ws_client: WebSocketGenerator, + media_content_type: MediaType, + category: str, + id_key: str, + first_item: tuple[str, MediaClass, MediaType, str, bool], ) -> None: - """Test a search inside an artist returns matching albums and tracks.""" + """Test a search inside an artist or genre also matches track titles.""" async def mock_browse( category: str, @@ -274,10 +297,9 @@ async def test_async_search_media_in_artist( search_query: str | None = None, ) -> dict[str, Any]: items = { - "artist": [{"id": "1", "title": "Love Album"}], "titles": [{"id": "2", "title": "Love Song"}], - } - return {"title": category, "items": items[category]} + }.get(category, [{"id": "1", "title": first_item[0]}]) + return {"title": category, "items": items} player = (await lms.async_get_players())[0] player.async_browse.side_effect = mock_browse @@ -289,7 +311,7 @@ async def test_async_search_media_in_artist( "type": "media_player/search_media", "entity_id": "media_player.test_player", "media_content_id": "42", - "media_content_type": MediaType.ARTIST, + "media_content_type": media_content_type, "search_query": "love", } ) @@ -305,12 +327,12 @@ async def test_async_search_media_in_artist( ) for item in response["result"]["result"] ] == [ - ("Love Album", MediaClass.ALBUM, MediaType.ALBUM, "1", True), + first_item, ("Love Song", MediaClass.TRACK, MediaType.TRACK, "2", False), ] assert player.async_browse.call_args_list == [ - call("artist", limit=ANY, browse_id=("artist_id", "42"), search_query="love"), - call("titles", limit=ANY, browse_id=("artist_id", "42"), search_query="love"), + call(category, limit=ANY, browse_id=(id_key, "42"), search_query="love"), + call("titles", limit=ANY, browse_id=(id_key, "42"), search_query="love"), ] @@ -345,7 +367,8 @@ async def test_async_search_media_invalid_filter( "media_content_type", [ pytest.param("Fake Type", id="unknown"), - pytest.param("artist tracks", id="internal"), + pytest.param("artist tracks", id="internal_artist"), + pytest.param("genre tracks", id="internal_genre"), ], ) async def test_async_search_media_invalid_type( @@ -375,6 +398,7 @@ async def test_async_search_media_invalid_type( err_message = "If specified, Media content type must be one of" assert err_message in response["error"]["message"] assert "artist tracks" not in response["error"]["message"] + assert "genre tracks" not in response["error"]["message"] async def test_async_search_media_not_found(