diff --git a/ui/src/artist/ArtistActions.test.jsx b/ui/src/artist/ArtistActions.test.jsx index 90be28409..a11ee50e3 100644 --- a/ui/src/artist/ArtistActions.test.jsx +++ b/ui/src/artist/ArtistActions.test.jsx @@ -49,11 +49,21 @@ describe('ArtistActions', () => { // Mock console.error to suppress error logging in tests vi.spyOn(console, 'error').mockImplementation(() => {}) + const songWithReplayGain = { + id: 'rec1', + replayGain: { + albumGain: -5, + albumPeak: 1, + trackGain: -6, + trackPeak: 0.8, + }, + } + subsonic.getSimilarSongs2.mockResolvedValue({ json: { 'subsonic-response': { status: 'ok', - similarSongs2: { song: [{ id: 'rec1' }] }, + similarSongs2: { song: [songWithReplayGain] }, }, }, }) @@ -61,7 +71,7 @@ describe('ArtistActions', () => { json: { 'subsonic-response': { status: 'ok', - topSongs: { song: [{ id: 'rec1' }] }, + topSongs: { song: [songWithReplayGain] }, }, }, }) @@ -93,6 +103,22 @@ describe('ArtistActions', () => { ) expect(mockDispatch).toHaveBeenCalled() }) + + it('maps replaygain info', async () => { + renderArtistActions() + clickActionButton('radio') + + await waitFor(() => + expect(subsonic.getSimilarSongs2).toHaveBeenCalledWith('ar1', 100), + ) + const action = mockDispatch.mock.calls[0][0] + expect(action.data.rec1).toMatchObject({ + rgAlbumGain: -5, + rgAlbumPeak: 1, + rgTrackGain: -6, + rgTrackPeak: 0.8, + }) + }) }) describe('Play action', () => { @@ -106,6 +132,22 @@ describe('ArtistActions', () => { expect(mockDispatch).toHaveBeenCalled() }) + it('maps replaygain info for top songs', async () => { + renderArtistActions() + clickActionButton('topSongs') + + await waitFor(() => + expect(subsonic.getTopSongs).toHaveBeenCalledWith('Artist', 100), + ) + const action = mockDispatch.mock.calls[0][0] + expect(action.data.rec1).toMatchObject({ + rgAlbumGain: -5, + rgAlbumPeak: 1, + rgTrackGain: -6, + rgTrackPeak: 0.8, + }) + }) + it('handles API rejection', async () => { subsonic.getTopSongs.mockRejectedValue(new Error('Network error')) diff --git a/ui/src/artist/actions.js b/ui/src/artist/actions.js index 0ab648fa0..6a8fbd9c6 100644 --- a/ui/src/artist/actions.js +++ b/ui/src/artist/actions.js @@ -1,6 +1,32 @@ import subsonic from '../subsonic/index.js' import { playTracks } from '../actions/index.js' +const mapReplayGain = (song) => { + const { replayGain: rg } = song + if (!rg) { + return song + } + + return { + ...song, + ...(rg.albumGain !== undefined && { rgAlbumGain: rg.albumGain }), + ...(rg.albumPeak !== undefined && { rgAlbumPeak: rg.albumPeak }), + ...(rg.trackGain !== undefined && { rgTrackGain: rg.trackGain }), + ...(rg.trackPeak !== undefined && { rgTrackPeak: rg.trackPeak }), + } +} + +const processSongsForPlayback = (songs) => { + const songData = {} + const ids = [] + songs.forEach((s) => { + const song = mapReplayGain(s) + songData[song.id] = song + ids.push(song.id) + }) + return { songData, ids } +} + export const playTopSongs = async (dispatch, notify, artistName) => { const res = await subsonic.getTopSongs(artistName, 100) const data = res.json['subsonic-response'] @@ -17,12 +43,7 @@ export const playTopSongs = async (dispatch, notify, artistName) => { return } - const songData = {} - const ids = [] - songs.forEach((s) => { - songData[s.id] = s - ids.push(s.id) - }) + const { songData, ids } = processSongsForPlayback(songs) dispatch(playTracks(songData, ids)) } @@ -42,12 +63,7 @@ export const playSimilar = async (dispatch, notify, id) => { return } - const songData = {} - const ids = [] - songs.forEach((s) => { - songData[s.id] = s - ids.push(s.id) - }) + const { songData, ids } = processSongsForPlayback(songs) dispatch(playTracks(songData, ids)) }