import { render, act } from '@testing-library/react'
import SharePlayer, { DOWNLOAD_FEEDBACK_MS } from './SharePlayer'
let playerProps
let renderCount
vi.mock('navidrome-music-player', () => ({
default: (props) => {
playerProps = props
renderCount++
return
},
}))
vi.mock('../config', () => ({
default: { enableDownloads: true },
shareInfo: {
id: 'share-1',
downloadable: true,
tracks: [{ id: 't1', title: 'One', artist: 'A', duration: 100 }],
},
}))
vi.mock('../utils', () => ({
shareDownloadUrl: (id) => `/share/d/${id}`,
shareStreamUrl: (id) => `/share/s/${id}`,
shareCoverUrl: (id) => `/share/img/${id}`,
}))
describe('SharePlayer', () => {
let clickSpy
beforeEach(() => {
vi.useFakeTimers()
playerProps = null
renderCount = 0
// Downloading for real would navigate the jsdom window.
clickSpy = vi
.spyOn(HTMLAnchorElement.prototype, 'click')
.mockImplementation(() => {})
})
afterEach(() => {
vi.runOnlyPendingTimers()
vi.useRealTimers()
vi.restoreAllMocks()
})
it('downloads via an anchor so the service worker does not intercept it', () => {
render()
let anchor
clickSpy.mockImplementation(function () {
anchor = { href: this.href, download: this.download }
})
act(() => {
playerProps.customDownloader()
})
expect(clickSpy).toHaveBeenCalledTimes(1)
expect(anchor.href).toContain('/share/d/share-1')
// Empty, so the server's Content-Disposition filename wins.
expect(anchor.download).toBe('')
})
it('removes the anchor from the document after clicking', () => {
render()
act(() => {
playerProps.customDownloader()
})
expect(document.querySelectorAll('a[download]')).toHaveLength(0)
})
// The inert styling itself is driven by JSS function values, which jsdom does
// not evaluate; it is verified in a browser. What is checked here is the
// state machine feeding it -- that the component re-renders on download and
// again when the window closes.
it('re-renders when the feedback window opens and closes', () => {
render()
const beforeDownload = renderCount
act(() => {
playerProps.customDownloader()
})
expect(renderCount).toBeGreaterThan(beforeDownload)
const beforeExpiry = renderCount
act(() => {
vi.runAllTimers()
})
expect(renderCount).toBeGreaterThan(beforeExpiry)
})
it('restarts the feedback window on a repeat download', () => {
render()
act(() => {
playerProps.customDownloader()
})
const elapsedBeforeRepeat = Math.floor(DOWNLOAD_FEEDBACK_MS / 2)
act(() => {
vi.advanceTimersByTime(elapsedBeforeRepeat)
})
act(() => {
playerProps.customDownloader()
})
// Past the first timer's deadline, which it would have fired at had the
// repeat download not replaced it.
const beforeOriginalDeadline = renderCount
act(() => {
vi.advanceTimersByTime(DOWNLOAD_FEEDBACK_MS - elapsedBeforeRepeat + 1)
})
expect(renderCount).toBe(beforeOriginalDeadline)
act(() => {
vi.runAllTimers()
})
expect(renderCount).toBeGreaterThan(beforeOriginalDeadline)
})
it('does not update state after unmount', () => {
const { unmount } = render()
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
act(() => {
playerProps.customDownloader()
})
unmount()
act(() => {
vi.runAllTimers()
})
expect(errorSpy).not.toHaveBeenCalled()
})
})