mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-18 07:30:29 +00:00
fix(ui): keep the placeholder for a cover whose fetch failed
useImageUrl caches a failed fetch as an entry with no blob, so treating every cached entry as instantly painted suppressed the placeholder on remount: the cover became an empty box instead of falling back to its thumbhash. Only a cached blob counts as instant. Caught by Codex on the previous commit, which introduced fromCache with the looser predicate.
This commit is contained in:
parent
a4ab19d605
commit
a4e78d949f
@ -192,6 +192,18 @@ describe('Artwork', () => {
|
||||
expect(onClick).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('shows the placeholder for a cached fetch failure, which has no blob', () => {
|
||||
useImageUrl.mockReturnValue({
|
||||
imgUrl: null,
|
||||
loading: false,
|
||||
error: true,
|
||||
fromCache: false,
|
||||
})
|
||||
const { container } = render(<Artwork record={withArt} />)
|
||||
expect(container.querySelector('img')).toBeNull()
|
||||
expect(container.querySelector('canvas')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('falls back to the placeholder when a refresh swaps in an uncached hash', () => {
|
||||
useImageUrl.mockReturnValue({
|
||||
imgUrl: 'blob:old',
|
||||
|
||||
@ -43,16 +43,16 @@ export const useImageUrl = (url) => {
|
||||
const [error, setError] = useState(cached?.error || false)
|
||||
const abortedRef = useRef(false)
|
||||
|
||||
// Resync during render, not in the effect below: for one render otherwise imgUrl still holds the
|
||||
// previous url's blob, so only fromCache can answer "was this url already cached when it started".
|
||||
const [fromCache, setFromCache] = useState(!!cached)
|
||||
// Resync during render: otherwise imgUrl holds the previous url's blob for one render, so only
|
||||
// fromCache can answer "already cached" - and a remembered failure has no blob to paint.
|
||||
const [fromCache, setFromCache] = useState(!!cached?.blobUrl)
|
||||
const [trackedUrl, setTrackedUrl] = useState(url)
|
||||
if (trackedUrl !== url) {
|
||||
setTrackedUrl(url)
|
||||
setImgUrl(cached?.blobUrl || null)
|
||||
setLoading(!!url && !cached)
|
||||
setError(cached?.error || false)
|
||||
setFromCache(!!cached)
|
||||
setFromCache(!!cached?.blobUrl)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -200,6 +200,7 @@ describe('useImageUrl', () => {
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1)
|
||||
expect(result2.current.imgUrl).toBe('blob:mock-url')
|
||||
expect(result2.current.loading).toBe(false)
|
||||
expect(result2.current.fromCache).toBe(true)
|
||||
})
|
||||
|
||||
it('should cache errors and not re-fetch broken URLs', async () => {
|
||||
@ -230,5 +231,7 @@ describe('useImageUrl', () => {
|
||||
expect(result2.current.error).toBe(true)
|
||||
expect(result2.current.imgUrl).toBeNull()
|
||||
expect(result2.current.loading).toBe(false)
|
||||
// A remembered failure has no blob, so callers must not treat it as instantly painted.
|
||||
expect(result2.current.fromCache).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user