feat(ui): add 'Show in Playlist' context menu (#4139)
* Update song playlist menu and endpoint * feat(ui): show submenu on click, not on hover Signed-off-by: Deluan <deluan@navidrome.org> * feat(ui): integrate dataProvider for fetching playlists in song context menu Signed-off-by: Deluan <deluan@navidrome.org> * feat(ui): update song context menu to use dataProvider for fetching playlists and inspecting songs Signed-off-by: Deluan <deluan@navidrome.org> * feat(ui): stop event propagation when closing playlist submenu Signed-off-by: Deluan <deluan@navidrome.org> * feat(ui): add 'show in playlist' option to options object Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { TestContext } from 'ra-test'
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { SongContextMenu } from './SongContextMenu'
|
||||
|
||||
vi.mock('../dataProvider', () => ({
|
||||
httpClient: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('react-redux', () => ({ useDispatch: () => vi.fn() }))
|
||||
|
||||
vi.mock('react-admin', async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
return {
|
||||
...actual,
|
||||
useRedirect: () => (url) => {
|
||||
window.location.hash = `#${url}`
|
||||
},
|
||||
useDataProvider: () => ({
|
||||
getPlaylists: vi.fn().mockResolvedValue({
|
||||
data: [{ id: 'pl1', name: 'Pl 1' }],
|
||||
}),
|
||||
inspect: vi.fn().mockResolvedValue({
|
||||
data: { rawTags: {} },
|
||||
}),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
describe('SongContextMenu', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
window.location.hash = ''
|
||||
})
|
||||
|
||||
it('navigates to playlist when selected', async () => {
|
||||
render(
|
||||
<TestContext>
|
||||
<SongContextMenu record={{ id: 'song1', size: 1 }} resource="song" />
|
||||
</TestContext>,
|
||||
)
|
||||
fireEvent.click(screen.getAllByRole('button')[1])
|
||||
await waitFor(() =>
|
||||
screen.getByText(/resources\.song\.actions\.showInPlaylist/),
|
||||
)
|
||||
fireEvent.click(
|
||||
screen.getByText(/resources\.song\.actions\.showInPlaylist/),
|
||||
)
|
||||
await waitFor(() => screen.getByText('Pl 1'))
|
||||
fireEvent.click(screen.getByText('Pl 1'))
|
||||
expect(window.location.hash).toBe('#/playlist/pl1/show')
|
||||
})
|
||||
|
||||
it('stops event propagation when playlist submenu is closed', async () => {
|
||||
const mockOnClick = vi.fn()
|
||||
render(
|
||||
<TestContext>
|
||||
<div onClick={mockOnClick}>
|
||||
<SongContextMenu record={{ id: 'song1', size: 1 }} resource="song" />
|
||||
</div>
|
||||
</TestContext>,
|
||||
)
|
||||
|
||||
// Open main menu
|
||||
fireEvent.click(screen.getAllByRole('button')[1])
|
||||
await waitFor(() =>
|
||||
screen.getByText(/resources\.song\.actions\.showInPlaylist/),
|
||||
)
|
||||
|
||||
// Open playlist submenu
|
||||
fireEvent.click(
|
||||
screen.getByText(/resources\.song\.actions\.showInPlaylist/),
|
||||
)
|
||||
await waitFor(() => screen.getByText('Pl 1'))
|
||||
|
||||
// Click outside the playlist submenu (should close it without triggering parent click)
|
||||
fireEvent.click(document.body)
|
||||
|
||||
expect(mockOnClick).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user