diff --git a/ui/src/share/SharePlayer.test.jsx b/ui/src/share/SharePlayer.test.jsx
new file mode 100644
index 000000000..fa37495c5
--- /dev/null
+++ b/ui/src/share/SharePlayer.test.jsx
@@ -0,0 +1,293 @@
+/* eslint-disable no-import-assign */
+import React from 'react'
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
+
+// Mock ReactJkMusicPlayer - capture the customDownloader prop so tests can invoke it
+let capturedCustomDownloader = null
+vi.mock('navidrome-music-player', () => ({
+ default: vi.fn(({ customDownloader, className }) => {
+ capturedCustomDownloader = customDownloader
+ return
+ }),
+}))
+
+vi.mock('../config', () => ({
+ default: {
+ defaultLanguage: '',
+ enableDownloads: true,
+ publicBaseUrl: '/share',
+ baseUrl: 'https://example.com',
+ },
+}))
+
+vi.mock('../utils', async (importOriginal) => {
+ const orig = await importOriginal()
+ return {
+ baseUrl: orig.baseUrl,
+ shareStreamUrl: (id) => `/share/s/${id}`,
+ shareCoverUrl: (id) => `/share/img/${id}`,
+ shareDownloadUrl: (id) => `/share/d/${id}`,
+ toDownloadUrl: (src) => `${src}?download=true`,
+ }
+})
+
+vi.mock('react-admin', async (importOriginal) => {
+ const actual = await importOriginal()
+ return {
+ ...actual,
+ useTranslate: () => (key) => key,
+ }
+})
+
+vi.mock('../dialogs/DialogTitle', () => ({
+ DialogTitle: ({ children, onClose }) => (
+
+ {children}
+
+
+ ),
+}))
+
+vi.mock('../dataProvider', async (importOriginal) => ({
+ ...(await importOriginal()),
+ httpClient: vi.fn(),
+}))
+
+// Spy on document.createElement to intercept anchor clicks
+let createdLinks = []
+const originalCreateElement = document.createElement.bind(document)
+
+import { TestContext } from 'ra-test'
+import SharePlayer from './SharePlayer'
+import * as configModule from '../config'
+
+describe('SharePlayer', () => {
+ beforeEach(async () => {
+ capturedCustomDownloader = null
+ createdLinks = []
+ configModule.shareInfo = {
+ id: 'share-1',
+ description: 'My Playlist',
+ downloadable: true,
+ tracks: [
+ {
+ id: 'track-1',
+ title: 'Song One',
+ artist: 'Artist One',
+ duration: 180,
+ },
+ {
+ id: 'track-2',
+ title: 'Song Two',
+ artist: 'Artist Two',
+ duration: 240,
+ },
+ ],
+ }
+
+ vi.spyOn(document, 'createElement').mockImplementation((tag) => {
+ const el = originalCreateElement(tag)
+ if (tag === 'a') {
+ vi.spyOn(el, 'click').mockImplementation(() => {})
+ createdLinks.push(el)
+ }
+ return el
+ })
+ })
+
+ afterEach(() => {
+ vi.restoreAllMocks()
+ configModule.shareInfo = null
+ })
+
+ it('renders the music player', () => {
+ render(
+
+
+ ,
+ )
+ expect(screen.getByTestId('music-player')).toBeInTheDocument()
+ })
+
+ it('does not show the download dialog initially', () => {
+ render(
+
+
+ ,
+ )
+ // Dialog is closed: the MUI Dialog root should not be present or should be hidden
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+ })
+
+ describe('customDownloader with multiple tracks', () => {
+ it('opens the download dialog when customDownloader is called', () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+
+ expect(
+ screen.getByText('resources.share.actions.download.title'),
+ ).toBeInTheDocument()
+ })
+
+ it('shows "Current Track" and "All Tracks" buttons in the dialog', () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+
+ expect(
+ screen.getByText('resources.share.actions.download.currentTrack'),
+ ).toBeInTheDocument()
+ expect(
+ screen.getByText('resources.share.actions.download.allTracks'),
+ ).toBeInTheDocument()
+ })
+
+ it('downloads the current track when "Current Track" is clicked', () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+
+ fireEvent.click(
+ screen.getByText('resources.share.actions.download.currentTrack'),
+ )
+
+ expect(createdLinks).toHaveLength(1)
+ expect(createdLinks[0].href).toContain('download=true')
+ expect(createdLinks[0].click).toHaveBeenCalled()
+ expect(createdLinks[0]).not.toBeInTheDocument() // Link should be removed after click
+ })
+
+ it('closes the dialog after clicking "Current Track"', async () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+ fireEvent.click(
+ screen.getByText('resources.share.actions.download.currentTrack'),
+ )
+
+ await waitFor(() =>
+ expect(
+ screen.queryByText('resources.share.actions.download.title'),
+ ).not.toBeInTheDocument(),
+ )
+ })
+
+ it('downloads all tracks as zip when "All Tracks" is clicked', () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+
+ fireEvent.click(
+ screen.getByText('resources.share.actions.download.allTracks'),
+ )
+
+ expect(createdLinks).toHaveLength(1)
+ expect(createdLinks[0].href).toContain('/share/d/share-1')
+ expect(createdLinks[0].download).toBe('My Playlist.zip')
+ expect(createdLinks[0].click).toHaveBeenCalled()
+ expect(createdLinks[0]).not.toBeInTheDocument()
+ })
+
+ it('closes the dialog after clicking "All Tracks"', async () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+ fireEvent.click(
+ screen.getByText('resources.share.actions.download.allTracks'),
+ )
+
+ await waitFor(() =>
+ expect(
+ screen.queryByText('resources.share.actions.download.title'),
+ ).not.toBeInTheDocument(),
+ )
+ })
+
+ it('closes the dialog when the close button is clicked', async () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+ expect(
+ screen.getByText('resources.share.actions.download.title'),
+ ).toBeInTheDocument()
+
+ fireEvent.click(screen.getByLabelText('close'))
+
+ await waitFor(() =>
+ expect(
+ screen.queryByText('resources.share.actions.download.title'),
+ ).not.toBeInTheDocument(),
+ )
+ })
+ })
+
+ describe('customDownloader with a single track', () => {
+ beforeEach(() => {
+ configModule.shareInfo = {
+ id: 'share-2',
+ description: 'Single Track',
+ downloadable: true,
+ tracks: [
+ {
+ id: 'track-1',
+ title: 'Only Song',
+ artist: 'Solo Artist',
+ duration: 200,
+ },
+ ],
+ }
+ })
+
+ it('downloads the track directly without opening a dialog', () => {
+ render(
+
+
+ ,
+ )
+
+ capturedCustomDownloader({ src: '/share/s/track-1' })
+
+ // Dialog title should NOT appear — direct download, no dialog
+ expect(
+ screen.queryByText('resources.share.actions.download.title'),
+ ).not.toBeInTheDocument()
+ // A link should have been clicked directly
+ expect(createdLinks).toHaveLength(1)
+ expect(createdLinks[0].href).toContain('download=true')
+ expect(createdLinks[0].click).toHaveBeenCalled()
+ expect(createdLinks[0]).not.toBeInTheDocument()
+ })
+ })
+})