mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
fix(ui): clear stale blurhash pixels and assert the draw path in tests
Clear the canvas before each decode attempt so a hash change that fails to decode doesn't leave the previous frame's pixels on screen once this wires into a list that recycles items. Also strengthen the specs to assert createImageData/putImageData were actually invoked (and with what), instead of only checking that a <canvas> element exists.
This commit is contained in:
parent
6ae855256c
commit
2510b06c4d
@ -12,17 +12,19 @@ export const BlurHashCanvas = ({ hash, className }) => {
|
||||
if (!hash || !canvasRef.current) {
|
||||
return
|
||||
}
|
||||
const ctx = canvasRef.current.getContext('2d')
|
||||
if (!ctx) {
|
||||
return
|
||||
}
|
||||
// Clear first so a hash change that fails to decode never leaves a stale frame.
|
||||
ctx.clearRect(0, 0, DECODE_SIZE, DECODE_SIZE)
|
||||
try {
|
||||
const pixels = decode(hash, DECODE_SIZE, DECODE_SIZE)
|
||||
const ctx = canvasRef.current.getContext('2d')
|
||||
if (!ctx) {
|
||||
return
|
||||
}
|
||||
const imageData = ctx.createImageData(DECODE_SIZE, DECODE_SIZE)
|
||||
imageData.data.set(pixels)
|
||||
ctx.putImageData(imageData, 0, 0)
|
||||
} catch {
|
||||
// A malformed hash simply leaves the canvas transparent.
|
||||
// A malformed hash simply leaves the canvas blank.
|
||||
}
|
||||
}, [hash])
|
||||
|
||||
|
||||
@ -3,15 +3,20 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { BlurHashCanvas } from './BlurHashCanvas'
|
||||
|
||||
describe('BlurHashCanvas', () => {
|
||||
// jsdom has no real 2D context; stub it so getContext doesn't hit its noisy "not implemented" path.
|
||||
// jsdom has no real 2D context; stub it (tracked) so specs can assert the draw path ran.
|
||||
let ctxMock
|
||||
let getContextSpy
|
||||
beforeEach(() => {
|
||||
ctxMock = {
|
||||
clearRect: vi.fn(),
|
||||
createImageData: vi.fn((w, h) => ({
|
||||
data: new Uint8ClampedArray(w * h * 4),
|
||||
})),
|
||||
putImageData: vi.fn(),
|
||||
}
|
||||
getContextSpy = vi
|
||||
.spyOn(HTMLCanvasElement.prototype, 'getContext')
|
||||
.mockReturnValue({
|
||||
createImageData: (w, h) => ({ data: new Uint8ClampedArray(w * h * 4) }),
|
||||
putImageData: () => {},
|
||||
})
|
||||
.mockReturnValue(ctxMock)
|
||||
})
|
||||
afterEach(() => {
|
||||
getContextSpy.mockRestore()
|
||||
@ -22,17 +27,35 @@ describe('BlurHashCanvas', () => {
|
||||
expect(container.querySelector('canvas')).toBeNull()
|
||||
})
|
||||
|
||||
it('renders a canvas for a valid hash', () => {
|
||||
it('decodes a valid hash and draws non-trivial pixel data', () => {
|
||||
const { container } = render(
|
||||
<BlurHashCanvas hash="LEHV6nWB2yk8pyo0adR*.7kCMdnj" />,
|
||||
)
|
||||
expect(container.querySelector('canvas')).not.toBeNull()
|
||||
expect(ctxMock.createImageData).toHaveBeenCalledWith(32, 32)
|
||||
expect(ctxMock.putImageData).toHaveBeenCalledTimes(1)
|
||||
const [imageData] = ctxMock.putImageData.mock.calls[0]
|
||||
expect(imageData.data.some((byte) => byte !== 0)).toBe(true)
|
||||
})
|
||||
|
||||
it('renders a canvas without throwing on a malformed hash', () => {
|
||||
it('renders a canvas without throwing on a malformed hash, and draws nothing', () => {
|
||||
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { container } = render(<BlurHashCanvas hash="!!!not-a-blurhash!!!" />)
|
||||
expect(container.querySelector('canvas')).not.toBeNull()
|
||||
expect(ctxMock.putImageData).not.toHaveBeenCalled()
|
||||
spy.mockRestore()
|
||||
})
|
||||
|
||||
it('clears the canvas when a hash change fails to decode', () => {
|
||||
const { rerender } = render(
|
||||
<BlurHashCanvas hash="LEHV6nWB2yk8pyo0adR*.7kCMdnj" />,
|
||||
)
|
||||
expect(ctxMock.putImageData).toHaveBeenCalledTimes(1)
|
||||
|
||||
rerender(<BlurHashCanvas hash="!!!not-a-blurhash!!!" />)
|
||||
|
||||
expect(ctxMock.clearRect).toHaveBeenCalledTimes(2)
|
||||
// No new pixels drawn after the clear, so the stale frame stays gone.
|
||||
expect(ctxMock.putImageData).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user