* Map replaygain info from getSimilarSongs2 * refactor: rename mapping function Signed-off-by: Deluan <deluan@navidrome.org> * refactor: Applied code review improvements Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
230 lines
6.2 KiB
JavaScript
230 lines
6.2 KiB
JavaScript
import React from 'react'
|
|
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
|
|
import { TestContext } from 'ra-test'
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import ArtistActions from './ArtistActions'
|
|
import subsonic from '../subsonic'
|
|
import { ThemeProvider, createTheme } from '@material-ui/core/styles'
|
|
|
|
const mockDispatch = vi.fn()
|
|
vi.mock('react-redux', () => ({ useDispatch: () => mockDispatch }))
|
|
|
|
vi.mock('../subsonic', () => ({
|
|
default: { getSimilarSongs2: vi.fn(), getTopSongs: vi.fn() },
|
|
}))
|
|
|
|
const mockNotify = vi.fn()
|
|
const mockGetList = vi.fn().mockResolvedValue({ data: [{ id: 's1' }] })
|
|
|
|
vi.mock('react-admin', async (importOriginal) => {
|
|
const actual = await importOriginal()
|
|
return {
|
|
...actual,
|
|
useNotify: () => mockNotify,
|
|
useDataProvider: () => ({ getList: mockGetList }),
|
|
useTranslate: () => (x) => x,
|
|
}
|
|
})
|
|
|
|
describe('ArtistActions', () => {
|
|
const defaultRecord = { id: 'ar1', name: 'Artist' }
|
|
|
|
const renderArtistActions = (record = defaultRecord) => {
|
|
const theme = createTheme()
|
|
return render(
|
|
<TestContext>
|
|
<ThemeProvider theme={theme}>
|
|
<ArtistActions record={record} />
|
|
</ThemeProvider>
|
|
</TestContext>,
|
|
)
|
|
}
|
|
|
|
const clickActionButton = (actionKey) => {
|
|
fireEvent.click(screen.getByText(`resources.artist.actions.${actionKey}`))
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
// 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: [songWithReplayGain] },
|
|
},
|
|
},
|
|
})
|
|
subsonic.getTopSongs.mockResolvedValue({
|
|
json: {
|
|
'subsonic-response': {
|
|
status: 'ok',
|
|
topSongs: { song: [songWithReplayGain] },
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
describe('Shuffle action', () => {
|
|
it('shuffles songs when clicked', async () => {
|
|
renderArtistActions()
|
|
clickActionButton('shuffle')
|
|
|
|
await waitFor(() =>
|
|
expect(mockGetList).toHaveBeenCalledWith('song', {
|
|
pagination: { page: 1, perPage: 500 },
|
|
sort: { field: 'random', order: 'ASC' },
|
|
filter: { album_artist_id: 'ar1', missing: false },
|
|
}),
|
|
)
|
|
expect(mockDispatch).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('Radio action', () => {
|
|
it('starts radio when clicked', async () => {
|
|
renderArtistActions()
|
|
clickActionButton('radio')
|
|
|
|
await waitFor(() =>
|
|
expect(subsonic.getSimilarSongs2).toHaveBeenCalledWith('ar1', 100),
|
|
)
|
|
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', () => {
|
|
it('plays top songs when clicked', async () => {
|
|
renderArtistActions()
|
|
clickActionButton('topSongs')
|
|
|
|
await waitFor(() =>
|
|
expect(subsonic.getTopSongs).toHaveBeenCalledWith('Artist', 100),
|
|
)
|
|
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'))
|
|
|
|
renderArtistActions()
|
|
clickActionButton('topSongs')
|
|
|
|
await waitFor(() =>
|
|
expect(subsonic.getTopSongs).toHaveBeenCalledWith('Artist', 100),
|
|
)
|
|
expect(mockNotify).toHaveBeenCalledWith('ra.page.error', 'warning')
|
|
expect(mockDispatch).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('handles failed API response', async () => {
|
|
subsonic.getTopSongs.mockResolvedValue({
|
|
json: {
|
|
'subsonic-response': {
|
|
status: 'failed',
|
|
error: { code: 40, message: 'Wrong username or password' },
|
|
},
|
|
},
|
|
})
|
|
|
|
renderArtistActions()
|
|
clickActionButton('topSongs')
|
|
|
|
await waitFor(() =>
|
|
expect(subsonic.getTopSongs).toHaveBeenCalledWith('Artist', 100),
|
|
)
|
|
expect(mockNotify).toHaveBeenCalledWith('ra.page.error', 'warning')
|
|
expect(mockDispatch).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('handles empty song list', async () => {
|
|
subsonic.getTopSongs.mockResolvedValue({
|
|
json: {
|
|
'subsonic-response': {
|
|
status: 'ok',
|
|
topSongs: { song: [] },
|
|
},
|
|
},
|
|
})
|
|
|
|
renderArtistActions()
|
|
clickActionButton('topSongs')
|
|
|
|
await waitFor(() =>
|
|
expect(subsonic.getTopSongs).toHaveBeenCalledWith('Artist', 100),
|
|
)
|
|
expect(mockNotify).toHaveBeenCalledWith(
|
|
'message.noTopSongsFound',
|
|
'warning',
|
|
)
|
|
expect(mockDispatch).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('handles missing topSongs property', async () => {
|
|
subsonic.getTopSongs.mockResolvedValue({
|
|
json: {
|
|
'subsonic-response': {
|
|
status: 'ok',
|
|
// topSongs property is missing
|
|
},
|
|
},
|
|
})
|
|
|
|
renderArtistActions()
|
|
clickActionButton('topSongs')
|
|
|
|
await waitFor(() =>
|
|
expect(subsonic.getTopSongs).toHaveBeenCalledWith('Artist', 100),
|
|
)
|
|
expect(mockNotify).toHaveBeenCalledWith(
|
|
'message.noTopSongsFound',
|
|
'warning',
|
|
)
|
|
expect(mockDispatch).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|