snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
329
packages/preact-query/src/__tests__/usePrefetchQuery.test.tsx
Normal file
329
packages/preact-query/src/__tests__/usePrefetchQuery.test.tsx
Normal file
@@ -0,0 +1,329 @@
|
||||
import { queryKey, sleep } from '@tanstack/query-test-utils'
|
||||
import { act, fireEvent } from '@testing-library/preact'
|
||||
import type { VNode } from 'preact'
|
||||
import { Suspense } from 'preact/compat'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
QueryCache,
|
||||
QueryClient,
|
||||
noop,
|
||||
usePrefetchQuery,
|
||||
useQueryErrorResetBoundary,
|
||||
useSuspenseQuery,
|
||||
} from '..'
|
||||
import { ErrorBoundary } from './ErrorBoundary'
|
||||
import { renderWithClient } from './utils'
|
||||
|
||||
describe('usePrefetchQuery', () => {
|
||||
let queryCache: QueryCache
|
||||
let queryClient: QueryClient
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
queryCache = new QueryCache()
|
||||
queryClient = new QueryClient({ queryCache })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
queryClient.clear()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('should prefetch query if query state does not exist', async () => {
|
||||
const queryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn: vi.fn(() => sleep(10).then(() => 'prefetchQuery')),
|
||||
}
|
||||
|
||||
const componentQueryOpts = {
|
||||
...queryOpts,
|
||||
queryFn: () => sleep(10).then(() => 'useSuspenseQuery'),
|
||||
}
|
||||
|
||||
function Page() {
|
||||
const state = useSuspenseQuery(componentQueryOpts)
|
||||
return <div>data: {String(state.data)}</div>
|
||||
}
|
||||
|
||||
function App() {
|
||||
usePrefetchQuery(queryOpts)
|
||||
|
||||
return (
|
||||
<Suspense fallback="Loading...">
|
||||
<Page />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
const rendered = renderWithClient(queryClient, <App />)
|
||||
|
||||
expect(rendered.getByText('Loading...')).toBeInTheDocument()
|
||||
|
||||
await vi.advanceTimersByTimeAsync(10)
|
||||
expect(rendered.getByText('data: prefetchQuery')).toBeInTheDocument()
|
||||
expect(queryOpts.queryFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not prefetch query if query state exists', async () => {
|
||||
const queryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn: vi.fn(() =>
|
||||
sleep(10).then(() => 'The usePrefetchQuery hook is smart!'),
|
||||
),
|
||||
}
|
||||
|
||||
function Page() {
|
||||
const state = useSuspenseQuery(queryOpts)
|
||||
return <div>data: {String(state.data)}</div>
|
||||
}
|
||||
|
||||
function App() {
|
||||
usePrefetchQuery(queryOpts)
|
||||
|
||||
return (
|
||||
<Suspense fallback="Loading...">
|
||||
<Page />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
void queryClient.query(queryOpts).catch(noop)
|
||||
await vi.advanceTimersByTimeAsync(10)
|
||||
queryOpts.queryFn.mockClear()
|
||||
const rendered = renderWithClient(queryClient, <App />)
|
||||
|
||||
expect(rendered.queryByText('fetching: true')).not.toBeInTheDocument()
|
||||
expect(
|
||||
rendered.getByText('data: The usePrefetchQuery hook is smart!'),
|
||||
).toBeInTheDocument()
|
||||
expect(queryOpts.queryFn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should let errors fall through and not refetch failed queries', async () => {
|
||||
const consoleMock = vi.spyOn(console, 'error')
|
||||
consoleMock.mockImplementation(() => undefined)
|
||||
const queryFn = vi.fn(() => sleep(10).then(() => 'Not an error'))
|
||||
|
||||
const queryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn,
|
||||
}
|
||||
|
||||
queryFn.mockImplementationOnce(() =>
|
||||
sleep(10).then(() => {
|
||||
throw new Error('Oops! Server error!')
|
||||
}),
|
||||
)
|
||||
|
||||
function Page() {
|
||||
const state = useSuspenseQuery(queryOpts)
|
||||
return <div>data: {String(state.data)}</div>
|
||||
}
|
||||
|
||||
function App() {
|
||||
usePrefetchQuery(queryOpts)
|
||||
|
||||
return (
|
||||
<ErrorBoundary fallbackRender={() => <div>Oops!</div>}>
|
||||
<Suspense fallback="Loading...">
|
||||
<Page />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
void queryClient.query(queryOpts).catch(noop)
|
||||
await vi.advanceTimersByTimeAsync(10)
|
||||
queryFn.mockClear()
|
||||
const rendered = renderWithClient(queryClient, <App />)
|
||||
|
||||
expect(rendered.getByText('Oops!')).toBeInTheDocument()
|
||||
expect(rendered.queryByText('data: Not an error')).not.toBeInTheDocument()
|
||||
expect(queryOpts.queryFn).not.toHaveBeenCalled()
|
||||
|
||||
consoleMock.mockRestore()
|
||||
})
|
||||
|
||||
it('should not create an endless loop when using inside a suspense boundary', async () => {
|
||||
const queryFn = vi.fn(() => sleep(10).then(() => 'prefetchedQuery'))
|
||||
|
||||
const queryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn,
|
||||
}
|
||||
|
||||
function Prefetch({ children }: { children: VNode }) {
|
||||
usePrefetchQuery(queryOpts)
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
function Page() {
|
||||
const state = useSuspenseQuery(queryOpts)
|
||||
return <div>data: {String(state.data)}</div>
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<></>}>
|
||||
<Prefetch>
|
||||
<Page />
|
||||
</Prefetch>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
const rendered = renderWithClient(queryClient, <App />)
|
||||
await vi.advanceTimersByTimeAsync(10)
|
||||
expect(rendered.getByText('data: prefetchedQuery')).toBeInTheDocument()
|
||||
expect(queryOpts.queryFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should be able to recover from errors and try fetching again', async () => {
|
||||
const consoleMock = vi.spyOn(console, 'error')
|
||||
consoleMock.mockImplementation(() => undefined)
|
||||
const queryFn = vi.fn(() =>
|
||||
sleep(10).then(() => 'This is fine :dog: :fire:'),
|
||||
)
|
||||
|
||||
const queryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn,
|
||||
}
|
||||
|
||||
queryFn.mockImplementationOnce(() =>
|
||||
sleep(10).then(() => {
|
||||
throw new Error('Oops! Server error!')
|
||||
}),
|
||||
)
|
||||
|
||||
function Page() {
|
||||
const state = useSuspenseQuery(queryOpts)
|
||||
return <div>data: {String(state.data)}</div>
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { reset } = useQueryErrorResetBoundary()
|
||||
usePrefetchQuery(queryOpts)
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
onReset={reset}
|
||||
fallbackRender={({ resetErrorBoundary }) => (
|
||||
<div>
|
||||
<div>Oops!</div>
|
||||
<button onClick={resetErrorBoundary}>Try again</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<Suspense fallback="Loading...">
|
||||
<Page />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
void queryClient.query(queryOpts).catch(noop)
|
||||
await vi.advanceTimersByTimeAsync(10)
|
||||
queryFn.mockClear()
|
||||
|
||||
const rendered = renderWithClient(queryClient, <App />)
|
||||
|
||||
expect(rendered.getByText('Oops!')).toBeInTheDocument()
|
||||
fireEvent.click(rendered.getByText('Try again'))
|
||||
await vi.advanceTimersByTimeAsync(10)
|
||||
expect(
|
||||
rendered.getByText('data: This is fine :dog: :fire:'),
|
||||
).toBeInTheDocument()
|
||||
expect(queryOpts.queryFn).toHaveBeenCalledTimes(1)
|
||||
consoleMock.mockRestore()
|
||||
})
|
||||
|
||||
it('should not create a suspense waterfall if prefetch is fired', async () => {
|
||||
const firstQueryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn: vi.fn(() => sleep(10).then(() => 'Prefetch is nice!')),
|
||||
}
|
||||
|
||||
const secondQueryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn: vi.fn(() => sleep(10).then(() => 'Prefetch is really nice!!')),
|
||||
}
|
||||
|
||||
const thirdQueryOpts = {
|
||||
queryKey: queryKey(),
|
||||
queryFn: vi.fn(() =>
|
||||
sleep(10).then(() => 'Prefetch does not create waterfalls!!'),
|
||||
),
|
||||
}
|
||||
|
||||
const Fallback = vi.fn().mockImplementation(() => <div>Loading...</div>)
|
||||
|
||||
function FirstQuery({ children }: { children?: VNode }) {
|
||||
const state = useSuspenseQuery(firstQueryOpts)
|
||||
return (
|
||||
<div>
|
||||
<div>data: {String(state.data)}</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SecondQuery({ children }: { children?: VNode }) {
|
||||
const state = useSuspenseQuery(secondQueryOpts)
|
||||
return (
|
||||
<div>
|
||||
<div>data: {String(state.data)}</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ThirdQuery() {
|
||||
const state = useSuspenseQuery(thirdQueryOpts)
|
||||
return <div>data: {String(state.data)}</div>
|
||||
}
|
||||
|
||||
function App() {
|
||||
usePrefetchQuery(firstQueryOpts)
|
||||
usePrefetchQuery(secondQueryOpts)
|
||||
usePrefetchQuery(thirdQueryOpts)
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Fallback />}>
|
||||
<FirstQuery>
|
||||
<SecondQuery>
|
||||
<ThirdQuery />
|
||||
</SecondQuery>
|
||||
</FirstQuery>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
const rendered = renderWithClient(queryClient, <App />)
|
||||
expect(
|
||||
queryClient.getQueryState(firstQueryOpts.queryKey)?.fetchStatus,
|
||||
).toBe('fetching')
|
||||
expect(
|
||||
queryClient.getQueryState(secondQueryOpts.queryKey)?.fetchStatus,
|
||||
).toBe('fetching')
|
||||
expect(
|
||||
queryClient.getQueryState(thirdQueryOpts.queryKey)?.fetchStatus,
|
||||
).toBe('fetching')
|
||||
expect(rendered.getByText('Loading...')).toBeInTheDocument()
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(10)
|
||||
})
|
||||
expect(rendered.getByText('data: Prefetch is nice!')).toBeInTheDocument()
|
||||
expect(
|
||||
rendered.getByText('data: Prefetch is really nice!!'),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
rendered.getByText('data: Prefetch does not create waterfalls!!'),
|
||||
).toBeInTheDocument()
|
||||
expect(Fallback).toHaveBeenCalledTimes(1)
|
||||
expect(firstQueryOpts.queryFn).toHaveBeenCalledTimes(1)
|
||||
expect(secondQueryOpts.queryFn).toHaveBeenCalledTimes(1)
|
||||
expect(thirdQueryOpts.queryFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user