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 { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { createStore, combineReducers } from 'redux'
|
||||
import { ThemeProvider } from '@material-ui/core/styles'
|
||||
import { createMuiTheme } from '@material-ui/core/styles'
|
||||
import { Player } from './Player'
|
||||
import { playerReducer } from '../reducers/playerReducer'
|
||||
import { settingsReducer } from '../reducers/settingsReducer'
|
||||
import { replayGainReducer } from '../reducers/replayGainReducer'
|
||||
import { render, screen, fireEvent, cleanup } from '@testing-library/react'
|
||||
import { useMediaQuery } from '@material-ui/core'
|
||||
import { useGetOne } from 'react-admin'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useToggleLove } from '../common'
|
||||
import { openSaveQueueDialog } from '../actions'
|
||||
import PlayerToolbar from './PlayerToolbar'
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../themes/useCurrentTheme', () => ({
|
||||
__esModule: true,
|
||||
default: () => ({
|
||||
player: { theme: 'dark' },
|
||||
}),
|
||||
}))
|
||||
|
||||
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('@material-ui/core', async () => {
|
||||
const actual = await import('@material-ui/core')
|
||||
return {
|
||||
...actual,
|
||||
useMediaQuery: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
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(() => {
|
||||
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', () => {
|
||||
renderPlayer()
|
||||
afterEach(cleanup)
|
||||
|
||||
expect(screen.getByTestId('react-jk-music-player')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('audio-title')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('player-toolbar')).toBeInTheDocument()
|
||||
describe('Desktop layout', () => {
|
||||
beforeEach(() => {
|
||||
useMediaQuery.mockReturnValue(true) // isDesktop = true
|
||||
})
|
||||
|
||||
it('should not render when not authenticated', () => {
|
||||
// Mock unauthenticated state
|
||||
const { useAuthState } = jest.requireMock('react-admin')
|
||||
useAuthState.mockReturnValue({ authenticated: false })
|
||||
it('renders desktop toolbar with both buttons', () => {
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
|
||||
const { container } = renderPlayer()
|
||||
expect(container.firstChild).toBeNull()
|
||||
// Both buttons should be in a single list item
|
||||
const listItems = screen.getAllByRole('listitem')
|
||||
expect(listItems).toHaveLength(1)
|
||||
|
||||
// Reset mock
|
||||
const { useAuthState: originalUseAuthState } =
|
||||
jest.requireMock('react-admin')
|
||||
originalUseAuthState.mockReturnValue({ authenticated: true })
|
||||
// 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('should not render when queue is empty', () => {
|
||||
const emptyStore = createStore(
|
||||
combineReducers({
|
||||
player: playerReducer,
|
||||
settings: settingsReducer,
|
||||
replayGain: replayGainReducer,
|
||||
}),
|
||||
{
|
||||
player: { queue: [] },
|
||||
settings: { notifications: true },
|
||||
replayGain: { gainMode: 'track' },
|
||||
},
|
||||
)
|
||||
it('disables save queue button when isRadio is true', () => {
|
||||
render(<PlayerToolbar id="song-1" isRadio={true} />)
|
||||
|
||||
const { container } = render(
|
||||
<Provider store={emptyStore}>
|
||||
<ThemeProvider theme={createMuiTheme()}>
|
||||
<Player />
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
)
|
||||
|
||||
expect(container.firstChild).toBeNull()
|
||||
const saveQueueButton = screen.getByTestId('save-queue-button')
|
||||
expect(saveQueueButton).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should have proper accessibility attributes', () => {
|
||||
renderPlayer()
|
||||
it('disables love button when conditions are met', () => {
|
||||
useGetOne.mockReturnValue({ data: mockSongData, loading: true })
|
||||
|
||||
const playerRegion = screen.getByRole('region')
|
||||
expect(playerRegion).toHaveAttribute('aria-label', 'player.audioPlayer')
|
||||
expect(playerRegion).toHaveAttribute('aria-live', 'polite')
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
|
||||
const loveButton = screen.getByTestId('love-button')
|
||||
expect(loveButton).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should render audio title with correct information', () => {
|
||||
renderPlayer()
|
||||
it('opens save queue dialog when save button is clicked', () => {
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
|
||||
expect(screen.getByTestId('audio-title')).toHaveTextContent('Song 1')
|
||||
})
|
||||
const saveQueueButton = screen.getByTestId('save-queue-button')
|
||||
fireEvent.click(saveQueueButton)
|
||||
|
||||
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()
|
||||
expect(mockDispatch).toHaveBeenCalledWith({
|
||||
type: 'OPEN_SAVE_QUEUE_DIALOG',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Mobile layout', () => {
|
||||
beforeEach(() => {
|
||||
useMediaQuery.mockReturnValue(false) // isDesktop = false
|
||||
})
|
||||
|
||||
it('renders mobile toolbar with buttons in separate list items', () => {
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
|
||||
// Each button should be in its own list item
|
||||
const listItems = screen.getAllByRole('listitem')
|
||||
expect(listItems).toHaveLength(2)
|
||||
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Common behavior', () => {
|
||||
it('renders global hotkeys in both layouts', () => {
|
||||
// Test desktop layout
|
||||
useMediaQuery.mockReturnValue(true)
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
expect(screen.getByTestId('global-hotkeys')).toBeInTheDocument()
|
||||
|
||||
// Cleanup and test mobile layout
|
||||
cleanup()
|
||||
useMediaQuery.mockReturnValue(false)
|
||||
render(<PlayerToolbar id="song-1" />)
|
||||
expect(screen.getByTestId('global-hotkeys')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('disables buttons when id is not provided', () => {
|
||||
render(<PlayerToolbar />)
|
||||
|
||||
const loveButton = screen.getByTestId('love-button')
|
||||
expect(loveButton).toBeDisabled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-env node */
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { usePlayerState } from './usePlayerState'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
@ -32,6 +34,7 @@ describe('usePlayerState', () => {
|
||||
const mockDispatch = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules()
|
||||
vi.clearAllMocks()
|
||||
useDispatch.mockReturnValue(mockDispatch)
|
||||
useSelector.mockReturnValue(mockPlayerState)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user