mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
118 lines
4.5 KiB
JavaScript
118 lines
4.5 KiB
JavaScript
import { render, fireEvent, act } from '@testing-library/react'
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
|
vi.mock('./useImageUrl', () => ({ useImageUrl: vi.fn() }))
|
|
vi.mock('../subsonic', () => ({
|
|
default: { getCoverArtUrl: () => '/rest/getCoverArt?id=al-1' },
|
|
}))
|
|
vi.mock('../config', () => ({ default: { uiCoverArtSize: 300 } }))
|
|
|
|
import { useImageUrl } from './useImageUrl'
|
|
import { Artwork } from './Artwork'
|
|
|
|
const withArt = {
|
|
id: 'al-1',
|
|
name: 'Album',
|
|
blurHash: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj',
|
|
}
|
|
|
|
describe('Artwork', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
// jsdom has no 2D context; stub it so BlurHashCanvas bails cleanly without console noise
|
|
HTMLCanvasElement.prototype.getContext = vi.fn(() => null)
|
|
})
|
|
|
|
it('renders nothing without a record', () => {
|
|
useImageUrl.mockReturnValue({ imgUrl: null, loading: false })
|
|
const { container } = render(<Artwork record={null} />)
|
|
expect(container.firstChild).toBeNull()
|
|
})
|
|
|
|
it('shows the blurhash and no <img> while loading', () => {
|
|
useImageUrl.mockReturnValue({ imgUrl: null, loading: true })
|
|
const { container } = render(<Artwork record={withArt} title="Album" />)
|
|
expect(container.querySelector('canvas')).not.toBeNull()
|
|
expect(container.querySelector('img')).toBeNull()
|
|
})
|
|
|
|
it('shows neither a broken <img> nor a canvas while loading a record with no blurhash', () => {
|
|
useImageUrl.mockReturnValue({ imgUrl: null, loading: true })
|
|
const { container } = render(<Artwork record={{ id: 'al-2', name: 'X' }} />)
|
|
expect(container.querySelector('img')).toBeNull()
|
|
expect(container.querySelector('canvas')).toBeNull()
|
|
})
|
|
|
|
it('mounts the image only once its blob is ready', () => {
|
|
useImageUrl.mockReturnValue({ imgUrl: 'blob:abc', loading: false })
|
|
const { container } = render(<Artwork record={withArt} title="Album" />)
|
|
const img = container.querySelector('img')
|
|
expect(img).not.toBeNull()
|
|
expect(img.getAttribute('src')).toBe('blob:abc')
|
|
})
|
|
|
|
it('keeps the blurhash under the image until the fade ends', () => {
|
|
vi.useFakeTimers()
|
|
try {
|
|
useImageUrl.mockReturnValue({ imgUrl: null, loading: true })
|
|
const { container, rerender } = render(<Artwork record={withArt} />)
|
|
|
|
// Blob arrives: the image mounts transparent, with the blurhash still behind it.
|
|
useImageUrl.mockReturnValue({ imgUrl: 'blob:abc', loading: false })
|
|
rerender(<Artwork record={withArt} />)
|
|
const img = container.querySelector('img')
|
|
expect(img).not.toBeNull()
|
|
expect(container.querySelector('canvas')).not.toBeNull()
|
|
|
|
// Decoding starts the cross-fade; the blurhash only goes away once it finishes. The clock
|
|
// drives it, not transitionend, which never fires under prefers-reduced-motion.
|
|
act(() => {
|
|
fireEvent.load(img)
|
|
})
|
|
expect(container.querySelector('canvas')).not.toBeNull()
|
|
act(() => {
|
|
fireEvent.transitionEnd(img)
|
|
})
|
|
expect(container.querySelector('canvas')).not.toBeNull()
|
|
act(() => {
|
|
vi.advanceTimersByTime(500)
|
|
})
|
|
expect(container.querySelector('canvas')).toBeNull()
|
|
} finally {
|
|
vi.useRealTimers()
|
|
}
|
|
})
|
|
|
|
it('does not fade an image that was already cached on mount', () => {
|
|
useImageUrl.mockReturnValue({ imgUrl: 'blob:abc', loading: false })
|
|
const { container } = render(<Artwork record={withArt} />)
|
|
// No placeholder to cross-fade from, so it paints at once.
|
|
expect(container.querySelector('canvas')).toBeNull()
|
|
expect(container.querySelector('img').className).toContain('imgInstant')
|
|
})
|
|
|
|
it('keeps the blurhash visible when the image never decodes', () => {
|
|
useImageUrl.mockReturnValue({ imgUrl: null, loading: true })
|
|
const { container, rerender } = render(<Artwork record={withArt} />)
|
|
useImageUrl.mockReturnValue({ imgUrl: 'blob:abc', loading: false })
|
|
rerender(<Artwork record={withArt} />)
|
|
|
|
expect(container.querySelector('canvas')).not.toBeNull()
|
|
})
|
|
|
|
it('fires onClick only when the image is loaded', () => {
|
|
const onClick = vi.fn()
|
|
useImageUrl.mockReturnValue({ imgUrl: null, loading: true })
|
|
const { container, rerender } = render(
|
|
<Artwork record={withArt} onClick={onClick} />,
|
|
)
|
|
container.firstChild.click()
|
|
expect(onClick).not.toHaveBeenCalled()
|
|
|
|
useImageUrl.mockReturnValue({ imgUrl: 'blob:abc', loading: false })
|
|
rerender(<Artwork record={withArt} onClick={onClick} />)
|
|
container.firstChild.click()
|
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|