fix(ui): coalesce lyric refreshes with in-flight requests

Signed-off-by: ranokay <github@ranokay.com>
This commit is contained in:
ranokay 2026-10-17 12:39:09 +03:00
parent 1d01a7f787
commit 1022ffb56a
No known key found for this signature in database
3 changed files with 42 additions and 11 deletions

View File

@ -153,8 +153,13 @@ const useEnhancedLyrics = ({
const refresh = useCallback(() => {
if (!trackId || disabled || !requested) return
// Coalesce with an unsettled request: its answer is at least as fresh as
// one started now, and restarting it would strand slow responses behind
// the poll interval.
const entry = inFlight.get(cacheKey)
if (entry && !entry.settled) return
setRefreshToken((current) => current + 1)
}, [disabled, requested, trackId])
}, [cacheKey, disabled, requested, trackId])
useEffect(() => {
if (!trackId || disabled || !requested) {

View File

@ -344,6 +344,29 @@ describe('useEnhancedLyrics', () => {
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(2)
})
it('coalesces a refresh with a request that is still in flight', async () => {
const pending = createDeferred()
subsonic.getLyricsBySongId.mockReturnValueOnce(pending.promise)
const { result } = renderLyrics({ trackId: 'song-coalesce' })
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(1)
act(() => {
result.current.refresh()
})
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(1)
act(() => {
pending.resolve(
responseForEntries([sourceEntry('NetEase', 'NetEase line')]),
)
})
await expectLyric(result, 'NetEase line')
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(1)
})
it('keeps current lyrics when a refresh fails', async () => {
subsonic.getLyricsBySongId
.mockResolvedValueOnce(

View File

@ -195,29 +195,32 @@ describe('usePlayerLyrics', () => {
vi.useFakeTimers()
subsonic.getLyricsBySongId.mockResolvedValue(variantsResponse())
const flushMicrotasks = () =>
act(async () => {
await Promise.resolve()
await Promise.resolve()
})
const { result } = renderPlayerLyrics()
act(() => result.current.toolbarLyricsProps.onToggleLyrics())
await act(async () => {
await Promise.resolve()
})
await flushMicrotasks()
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(1)
act(() => result.current.desktopLyricsProps.onSourceMenuOpenChange(true))
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(2)
await act(async () => {
vi.advanceTimersByTime(5_000)
})
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(3)
for (let step = 0; step < 11; step += 1) {
let expectedCalls = 2
for (let step = 0; step < 12; step += 1) {
await flushMicrotasks()
await act(async () => {
vi.advanceTimersByTime(5_000)
})
expectedCalls += 1
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(expectedCalls)
}
expect(subsonic.getLyricsBySongId).toHaveBeenCalledTimes(14)
act(() => result.current.desktopLyricsProps.onSourceMenuOpenChange(false))
await flushMicrotasks()
await act(async () => {
vi.advanceTimersByTime(15_000)
})