Move neighbor lookup into a dedicated service and let the viewer load adjacent artworks after first paint instead of blocking the detail request.
80 lines
3.4 KiB
React
80 lines
3.4 KiB
React
import React from 'react'
|
|
import { act, cleanup, render, waitFor } from '@testing-library/react'
|
|
import ArtworkNavigator from './ArtworkNavigator'
|
|
|
|
describe('ArtworkNavigator preview prefetch', () => {
|
|
afterEach(() => {
|
|
cleanup()
|
|
vi.restoreAllMocks()
|
|
delete window.requestIdleCallback
|
|
delete window.cancelIdleCallback
|
|
})
|
|
|
|
it('seeds neighbor prefetch from navigation and keeps full navigation separate', async () => {
|
|
let idleCallback
|
|
let ready
|
|
window.requestIdleCallback = vi.fn((callback) => { idleCallback = callback; return 1 })
|
|
window.cancelIdleCallback = vi.fn()
|
|
global.fetch = vi.fn((url) => {
|
|
if (String(url).includes('/api/artworks/navigation/777')) {
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: async () => ({
|
|
prev_id: 776,
|
|
next_id: 778,
|
|
prev_url: '/art/776/previous',
|
|
next_url: '/art/778/next',
|
|
prev_slug: 'previous',
|
|
next_slug: 'next',
|
|
prev_preview: { id: 776, title: 'Previous', slug: 'previous', thumbs: { md: { url: '/md-776' }, lg: { url: '/lg-776' } } },
|
|
next_preview: { id: 778, title: 'Next', slug: 'next', thumbs: { md: { url: '/md-778' }, lg: { url: '/lg-778' } } },
|
|
}),
|
|
})
|
|
}
|
|
|
|
return Promise.resolve({ ok: true, json: async () => ({ id: 778, title: 'Next', slug: 'next' }) })
|
|
})
|
|
|
|
render(<ArtworkNavigator artworkId={777} onNavigate={vi.fn()} onReady={(next) => { if (next.hasNext) ready = next }} />)
|
|
await waitFor(() => expect(ready?.hasNext).toBe(true))
|
|
await act(async () => idleCallback())
|
|
|
|
expect(global.fetch.mock.calls.filter(([url]) => String(url).includes('?prefetch=1'))).toHaveLength(0)
|
|
await act(async () => ready.navigateNext())
|
|
expect(global.fetch).toHaveBeenCalledWith('/api/artworks/778/page', { headers: { Accept: 'application/json' } })
|
|
})
|
|
|
|
it('uses embedded initial navigation without a standalone or prefetch request', async () => {
|
|
let idleCallback
|
|
window.requestIdleCallback = vi.fn((callback) => { idleCallback = callback; return 1 })
|
|
window.cancelIdleCallback = vi.fn()
|
|
global.fetch = vi.fn()
|
|
const navigation = {
|
|
prev_id: 880,
|
|
next_id: 882,
|
|
prev_url: '/art/880/previous',
|
|
next_url: '/art/882/next',
|
|
prev_slug: 'previous',
|
|
next_slug: 'next',
|
|
prev_preview: { id: 880, title: 'Previous', slug: 'previous', thumbs: { md: { url: '/md-880' }, lg: { url: '/lg-880' } } },
|
|
next_preview: { id: 882, title: 'Next', slug: 'next', thumbs: { md: { url: '/md-882' }, lg: { url: '/lg-882' } } },
|
|
}
|
|
|
|
render(<ArtworkNavigator artworkId={881} navigation={navigation} onNavigate={vi.fn()} onReady={vi.fn()} />)
|
|
await waitFor(() => expect(window.requestIdleCallback).toHaveBeenCalledTimes(1))
|
|
await act(async () => idleCallback())
|
|
|
|
expect(global.fetch).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('falls back to standalone navigation for malformed embedded data', async () => {
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ prev_id: 889, next_id: 891, prev_url: '/art/889/p', next_url: '/art/891/n', prev_slug: 'p', next_slug: 'n' }),
|
|
})
|
|
|
|
render(<ArtworkNavigator artworkId={890} navigation={{ prev_id: 889 }} onNavigate={vi.fn()} onReady={vi.fn()} />)
|
|
await waitFor(() => expect(global.fetch).toHaveBeenCalledWith('/api/artworks/navigation/890', { headers: { Accept: 'application/json' } }))
|
|
})
|
|
})
|