mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
fix: test
This commit is contained in:
parent
efeaa80de9
commit
6a9ccb309c
@ -1,259 +1,166 @@
|
|||||||
/* eslint-env jest */
|
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
import { render, screen, fireEvent, cleanup } from '@testing-library/react'
|
||||||
import { Provider } from 'react-redux'
|
import { useMediaQuery } from '@material-ui/core'
|
||||||
import { createStore, combineReducers } from 'redux'
|
import { useGetOne } from 'react-admin'
|
||||||
import { ThemeProvider } from '@material-ui/core/styles'
|
import { useDispatch } from 'react-redux'
|
||||||
import { createMuiTheme } from '@material-ui/core/styles'
|
import { useToggleLove } from '../common'
|
||||||
import { Player } from './Player'
|
import { openSaveQueueDialog } from '../actions'
|
||||||
import { playerReducer } from '../reducers/playerReducer'
|
import PlayerToolbar from './PlayerToolbar'
|
||||||
import { settingsReducer } from '../reducers/settingsReducer'
|
|
||||||
import { replayGainReducer } from '../reducers/replayGainReducer'
|
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
jest.mock('../themes/useCurrentTheme', () => ({
|
vi.mock('@material-ui/core', async () => {
|
||||||
__esModule: true,
|
const actual = await import('@material-ui/core')
|
||||||
default: () => ({
|
return {
|
||||||
player: { theme: 'dark' },
|
...actual,
|
||||||
}),
|
useMediaQuery: vi.fn(),
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../config', () => ({
|
|
||||||
enableCoverAnimation: false,
|
|
||||||
gaTrackingId: null,
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('./AudioTitle', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: ({ audioInfo }) => (
|
|
||||||
<div data-testid="audio-title">{audioInfo?.song?.title || 'No song'}</div>
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('./PlayerToolbar', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: ({ id }) => <div data-testid="player-toolbar">{id || 'No ID'}</div>,
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('./locale', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: () => (key) => key,
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('./keyHandlers', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: () => ({}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../hotkeys', () => ({
|
|
||||||
keyMap: {},
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('react-ga', () => ({
|
|
||||||
event: jest.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../utils', () => ({
|
|
||||||
sendNotification: jest.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('navidrome-music-player', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: ({ children, ...props }) => (
|
|
||||||
<div data-testid="react-jk-music-player" {...props}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('navidrome-music-player/assets/index.css', () => {})
|
|
||||||
|
|
||||||
// Mock react-redux hooks
|
|
||||||
jest.mock('react-redux', () => ({
|
|
||||||
...jest.requireActual('react-redux'),
|
|
||||||
useSelector: jest.fn(),
|
|
||||||
useDispatch: jest.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock react-admin hooks
|
|
||||||
jest.mock('react-admin', () => ({
|
|
||||||
useAuthState: () => ({ authenticated: true }),
|
|
||||||
useDataProvider: () => ({
|
|
||||||
getOne: jest.fn().mockResolvedValue({ data: {} }),
|
|
||||||
}),
|
|
||||||
useTranslate: () => (key) => key,
|
|
||||||
createMuiTheme: jest.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock @material-ui/core
|
|
||||||
jest.mock('@material-ui/core', () => ({
|
|
||||||
...jest.requireActual('@material-ui/core'),
|
|
||||||
useMediaQuery: () => true, // Mock as desktop
|
|
||||||
ThemeProvider: ({ children }) => <div>{children}</div>,
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('Player Component', () => {
|
|
||||||
const mockStore = createStore(
|
|
||||||
combineReducers({
|
|
||||||
player: playerReducer,
|
|
||||||
settings: settingsReducer,
|
|
||||||
replayGain: replayGainReducer,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
player: {
|
|
||||||
queue: [
|
|
||||||
{
|
|
||||||
uuid: '1',
|
|
||||||
musicSrc: 'song1.mp3',
|
|
||||||
title: 'Song 1',
|
|
||||||
artist: 'Artist 1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
current: {
|
|
||||||
uuid: '1',
|
|
||||||
trackId: 'track1',
|
|
||||||
song: { title: 'Song 1', artist: 'Artist 1' },
|
|
||||||
},
|
|
||||||
playIndex: 0,
|
|
||||||
mode: 'single',
|
|
||||||
volume: 0.8,
|
|
||||||
clear: false,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
notifications: true,
|
|
||||||
},
|
|
||||||
replayGain: {
|
|
||||||
gainMode: 'track',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const renderPlayer = () => {
|
|
||||||
return render(
|
|
||||||
<Provider store={mockStore}>
|
|
||||||
<ThemeProvider theme={createMuiTheme()}>
|
|
||||||
<Player />
|
|
||||||
</ThemeProvider>
|
|
||||||
</Provider>,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('react-admin', () => ({
|
||||||
|
useGetOne: vi.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('react-redux', () => ({
|
||||||
|
useDispatch: vi.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('../common', () => ({
|
||||||
|
LoveButton: ({ className, disabled }) => (
|
||||||
|
<button data-testid="love-button" className={className} disabled={disabled}>
|
||||||
|
Love
|
||||||
|
</button>
|
||||||
|
),
|
||||||
|
useToggleLove: vi.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('../actions', () => ({
|
||||||
|
openSaveQueueDialog: vi.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('react-hotkeys', () => ({
|
||||||
|
GlobalHotKeys: () => <div data-testid="global-hotkeys" />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('<PlayerToolbar />', () => {
|
||||||
|
const mockToggleLove = vi.fn()
|
||||||
|
const mockDispatch = vi.fn()
|
||||||
|
const mockSongData = { id: 'song-1', name: 'Test Song', starred: false }
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
useGetOne.mockReturnValue({ data: mockSongData, loading: false })
|
||||||
|
useToggleLove.mockReturnValue([mockToggleLove, false])
|
||||||
|
useDispatch.mockReturnValue(mockDispatch)
|
||||||
|
openSaveQueueDialog.mockReturnValue({ type: 'OPEN_SAVE_QUEUE_DIALOG' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render player when authenticated with queue', () => {
|
afterEach(cleanup)
|
||||||
renderPlayer()
|
|
||||||
|
|
||||||
expect(screen.getByTestId('react-jk-music-player')).toBeInTheDocument()
|
describe('Desktop layout', () => {
|
||||||
expect(screen.getByTestId('audio-title')).toBeInTheDocument()
|
beforeEach(() => {
|
||||||
expect(screen.getByTestId('player-toolbar')).toBeInTheDocument()
|
useMediaQuery.mockReturnValue(true) // isDesktop = true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders desktop toolbar with both buttons', () => {
|
||||||
|
render(<PlayerToolbar id="song-1" />)
|
||||||
|
|
||||||
|
// Both buttons should be in a single list item
|
||||||
|
const listItems = screen.getAllByRole('listitem')
|
||||||
|
expect(listItems).toHaveLength(1)
|
||||||
|
|
||||||
|
// Verify both buttons are rendered
|
||||||
|
expect(screen.getByTestId('save-queue-button')).toBeInTheDocument()
|
||||||
|
expect(screen.getByTestId('love-button')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Verify desktop classes are applied
|
||||||
|
expect(listItems[0].className).toContain('toolbar')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables save queue button when isRadio is true', () => {
|
||||||
|
render(<PlayerToolbar id="song-1" isRadio={true} />)
|
||||||
|
|
||||||
|
const saveQueueButton = screen.getByTestId('save-queue-button')
|
||||||
|
expect(saveQueueButton).toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables love button when conditions are met', () => {
|
||||||
|
useGetOne.mockReturnValue({ data: mockSongData, loading: true })
|
||||||
|
|
||||||
|
render(<PlayerToolbar id="song-1" />)
|
||||||
|
|
||||||
|
const loveButton = screen.getByTestId('love-button')
|
||||||
|
expect(loveButton).toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('opens save queue dialog when save button is clicked', () => {
|
||||||
|
render(<PlayerToolbar id="song-1" />)
|
||||||
|
|
||||||
|
const saveQueueButton = screen.getByTestId('save-queue-button')
|
||||||
|
fireEvent.click(saveQueueButton)
|
||||||
|
|
||||||
|
expect(mockDispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'OPEN_SAVE_QUEUE_DIALOG',
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not render when not authenticated', () => {
|
describe('Mobile layout', () => {
|
||||||
// Mock unauthenticated state
|
beforeEach(() => {
|
||||||
const { useAuthState } = jest.requireMock('react-admin')
|
useMediaQuery.mockReturnValue(false) // isDesktop = false
|
||||||
useAuthState.mockReturnValue({ authenticated: false })
|
})
|
||||||
|
|
||||||
const { container } = renderPlayer()
|
it('renders mobile toolbar with buttons in separate list items', () => {
|
||||||
expect(container.firstChild).toBeNull()
|
render(<PlayerToolbar id="song-1" />)
|
||||||
|
|
||||||
// Reset mock
|
// Each button should be in its own list item
|
||||||
const { useAuthState: originalUseAuthState } =
|
const listItems = screen.getAllByRole('listitem')
|
||||||
jest.requireMock('react-admin')
|
expect(listItems).toHaveLength(2)
|
||||||
originalUseAuthState.mockReturnValue({ authenticated: true })
|
|
||||||
|
// Verify both buttons are rendered
|
||||||
|
expect(screen.getByTestId('save-queue-button')).toBeInTheDocument()
|
||||||
|
expect(screen.getByTestId('love-button')).toBeInTheDocument()
|
||||||
|
|
||||||
|
// Verify mobile classes are applied
|
||||||
|
expect(listItems[0].className).toContain('mobileListItem')
|
||||||
|
expect(listItems[1].className).toContain('mobileListItem')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables save queue button when isRadio is true', () => {
|
||||||
|
render(<PlayerToolbar id="song-1" isRadio={true} />)
|
||||||
|
|
||||||
|
const saveQueueButton = screen.getByTestId('save-queue-button')
|
||||||
|
expect(saveQueueButton).toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('disables love button when conditions are met', () => {
|
||||||
|
useGetOne.mockReturnValue({ data: mockSongData, loading: true })
|
||||||
|
|
||||||
|
render(<PlayerToolbar id="song-1" />)
|
||||||
|
|
||||||
|
const loveButton = screen.getByTestId('love-button')
|
||||||
|
expect(loveButton).toBeDisabled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not render when queue is empty', () => {
|
describe('Common behavior', () => {
|
||||||
const emptyStore = createStore(
|
it('renders global hotkeys in both layouts', () => {
|
||||||
combineReducers({
|
// Test desktop layout
|
||||||
player: playerReducer,
|
useMediaQuery.mockReturnValue(true)
|
||||||
settings: settingsReducer,
|
render(<PlayerToolbar id="song-1" />)
|
||||||
replayGain: replayGainReducer,
|
expect(screen.getByTestId('global-hotkeys')).toBeInTheDocument()
|
||||||
}),
|
|
||||||
{
|
|
||||||
player: { queue: [] },
|
|
||||||
settings: { notifications: true },
|
|
||||||
replayGain: { gainMode: 'track' },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const { container } = render(
|
// Cleanup and test mobile layout
|
||||||
<Provider store={emptyStore}>
|
cleanup()
|
||||||
<ThemeProvider theme={createMuiTheme()}>
|
useMediaQuery.mockReturnValue(false)
|
||||||
<Player />
|
render(<PlayerToolbar id="song-1" />)
|
||||||
</ThemeProvider>
|
expect(screen.getByTestId('global-hotkeys')).toBeInTheDocument()
|
||||||
</Provider>,
|
})
|
||||||
)
|
|
||||||
|
|
||||||
expect(container.firstChild).toBeNull()
|
it('disables buttons when id is not provided', () => {
|
||||||
})
|
render(<PlayerToolbar />)
|
||||||
|
|
||||||
it('should have proper accessibility attributes', () => {
|
const loveButton = screen.getByTestId('love-button')
|
||||||
renderPlayer()
|
expect(loveButton).toBeDisabled()
|
||||||
|
})
|
||||||
const playerRegion = screen.getByRole('region')
|
|
||||||
expect(playerRegion).toHaveAttribute('aria-label', 'player.audioPlayer')
|
|
||||||
expect(playerRegion).toHaveAttribute('aria-live', 'polite')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render audio title with correct information', () => {
|
|
||||||
renderPlayer()
|
|
||||||
|
|
||||||
expect(screen.getByTestId('audio-title')).toHaveTextContent('Song 1')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should render player toolbar with track ID', () => {
|
|
||||||
renderPlayer()
|
|
||||||
|
|
||||||
expect(screen.getByTestId('player-toolbar')).toHaveTextContent('track1')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle mobile player detection', () => {
|
|
||||||
// Mock mobile detection
|
|
||||||
const { useMediaQuery } = jest.requireMock('@material-ui/core')
|
|
||||||
useMediaQuery.mockReturnValue(false) // Mobile
|
|
||||||
|
|
||||||
renderPlayer()
|
|
||||||
|
|
||||||
// Mobile-specific logic should be applied
|
|
||||||
// This would be tested more thoroughly with actual mobile behavior
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should update document title when not visible', () => {
|
|
||||||
// Mock empty queue to make player not visible
|
|
||||||
const emptyStore = createStore(
|
|
||||||
combineReducers({
|
|
||||||
player: playerReducer,
|
|
||||||
settings: settingsReducer,
|
|
||||||
replayGain: replayGainReducer,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
player: { queue: [] },
|
|
||||||
settings: { notifications: true },
|
|
||||||
replayGain: { gainMode: 'track' },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Provider store={emptyStore}>
|
|
||||||
<ThemeProvider theme={createMuiTheme()}>
|
|
||||||
<Player />
|
|
||||||
</ThemeProvider>
|
|
||||||
</Provider>,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Document title should be reset when player is not visible
|
|
||||||
expect(document.title).toBe('Navidrome')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should integrate with theme provider', () => {
|
|
||||||
renderPlayer()
|
|
||||||
|
|
||||||
// ThemeProvider should wrap the component
|
|
||||||
expect(
|
|
||||||
screen.getByTestId('react-jk-music-player').parentElement,
|
|
||||||
).toBeInTheDocument()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
import { renderHook } from '@testing-library/react-hooks'
|
import { renderHook } from '@testing-library/react-hooks'
|
||||||
import { usePlayerState } from './usePlayerState'
|
import { usePlayerState } from './usePlayerState'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
@ -32,6 +34,7 @@ describe('usePlayerState', () => {
|
|||||||
const mockDispatch = vi.fn()
|
const mockDispatch = vi.fn()
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.resetModules()
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
useDispatch.mockReturnValue(mockDispatch)
|
useDispatch.mockReturnValue(mockDispatch)
|
||||||
useSelector.mockReturnValue(mockPlayerState)
|
useSelector.mockReturnValue(mockPlayerState)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user