Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(react-query): use fake timer for HydrationBoundary.test.tsx #8774

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
150 changes: 77 additions & 73 deletions packages/react-query/src/__tests__/HydrationBoundary.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { beforeAll, describe, expect, test, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import * as React from 'react'
import { render } from '@testing-library/react'

import * as coreModule from '@tanstack/query-core'
import {
HydrationBoundary,
QueryCache,
QueryClient,
QueryClientProvider,
dehydrate,
Expand All @@ -14,33 +13,32 @@ import {
import { createQueryClient, sleep } from './utils'

describe('React hydration', () => {
const fetchData: (value: string) => Promise<string> = (value) =>
new Promise((res) => setTimeout(() => res(value), 10))
const dataQuery: (key: [string]) => Promise<string> = (key) =>
fetchData(key[0])
let stringifiedState: string

beforeAll(async () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
await queryClient.prefetchQuery({
beforeEach(async () => {
vi.useFakeTimers()
const queryClient = createQueryClient()
queryClient.prefetchQuery({
queryKey: ['string'],
queryFn: () => dataQuery(['stringCached']),
queryFn: () => sleep(10).then(() => ['stringCached']),
})
await vi.advanceTimersByTimeAsync(10)
const dehydrated = dehydrate(queryClient)
stringifiedState = JSON.stringify(dehydrated)
queryClient.clear()
})
afterEach(() => {
vi.useRealTimers()
})

test('should hydrate queries to the cache on context', async () => {
const dehydratedState = JSON.parse(stringifiedState)
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
const queryClient = createQueryClient()

function Page() {
const { data } = useQuery({
queryKey: ['string'],
queryFn: () => dataQuery(['string']),
queryFn: () => sleep(20).then(() => ['string']),
})
return (
<div>
Expand All @@ -56,25 +54,23 @@ describe('React hydration', () => {
</HydrationBoundary>
</QueryClientProvider>,
)

await rendered.findByText('stringCached')
await rendered.findByText('string')
await vi.advanceTimersByTimeAsync(1)
expect(rendered.getByText('stringCached')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(20)
expect(rendered.getByText('string')).toBeInTheDocument()
queryClient.clear()
})

test('should hydrate queries to the cache on custom context', async () => {
const queryCacheOuter = new QueryCache()
const queryCacheInner = new QueryCache()

const queryClientInner = new QueryClient({ queryCache: queryCacheInner })
const queryClientOuter = new QueryClient({ queryCache: queryCacheOuter })
const queryClientInner = new QueryClient()
const queryClientOuter = new QueryClient()

const dehydratedState = JSON.parse(stringifiedState)

function Page() {
const { data } = useQuery({
queryKey: ['string'],
queryFn: () => dataQuery(['string']),
queryFn: () => sleep(20).then(() => ['string']),
})
return (
<div>
Expand All @@ -93,8 +89,10 @@ describe('React hydration', () => {
</QueryClientProvider>,
)

await rendered.findByText('stringCached')
await rendered.findByText('string')
await vi.advanceTimersByTimeAsync(1)
expect(rendered.getByText('stringCached')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(20)
expect(rendered.getByText('string')).toBeInTheDocument()

queryClientInner.clear()
queryClientOuter.clear()
Expand All @@ -103,13 +101,12 @@ describe('React hydration', () => {
describe('ReactQueryCacheProvider with hydration support', () => {
test('should hydrate new queries if queries change', async () => {
const dehydratedState = JSON.parse(stringifiedState)
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
const queryClient = createQueryClient()

function Page({ queryKey }: { queryKey: [string] }) {
const { data } = useQuery({
queryKey,
queryFn: () => dataQuery(queryKey),
queryFn: () => sleep(20).then(() => queryKey),
})
return (
<div>
Expand All @@ -126,20 +123,23 @@ describe('React hydration', () => {
</QueryClientProvider>,
)

await rendered.findByText('string')
await vi.advanceTimersByTimeAsync(1)
expect(rendered.getByText('stringCached')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(20)
expect(rendered.getByText('string')).toBeInTheDocument()

const intermediateCache = new QueryCache()
const intermediateClient = createQueryClient({
queryCache: intermediateCache,
})
await intermediateClient.prefetchQuery({
const intermediateClient = createQueryClient()

intermediateClient.prefetchQuery({
queryKey: ['string'],
queryFn: () => dataQuery(['should change']),
queryFn: () => sleep(20).then(() => ['should change']),
})
await intermediateClient.prefetchQuery({
await vi.advanceTimersByTimeAsync(20)
intermediateClient.prefetchQuery({
queryKey: ['added'],
queryFn: () => dataQuery(['added']),
queryFn: () => sleep(20).then(() => ['added']),
})
await vi.advanceTimersByTimeAsync(20)
const dehydrated = dehydrate(intermediateClient)
intermediateClient.clear()

Expand All @@ -154,14 +154,14 @@ describe('React hydration', () => {

// Existing observer should not have updated at this point,
// as that would indicate a side effect in the render phase
rendered.getByText('string')
expect(rendered.getByText('string')).toBeInTheDocument()
// New query data should be available immediately
rendered.getByText('added')
expect(rendered.getByText('added')).toBeInTheDocument()

await sleep(10)
await vi.advanceTimersByTimeAsync(20)
// After effects phase has had time to run, the observer should have updated
expect(rendered.queryByText('string')).toBeNull()
rendered.getByText('should change')
expect(rendered.getByText('should change')).toBeInTheDocument()

queryClient.clear()
})
Expand All @@ -174,13 +174,12 @@ describe('React hydration', () => {
// since they don't have any observers on the current page that would update.
test('should hydrate new but not existing queries if transition is aborted', async () => {
const initialDehydratedState = JSON.parse(stringifiedState)
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
const queryClient = createQueryClient()

function Page({ queryKey }: { queryKey: [string] }) {
const { data } = useQuery({
queryKey,
queryFn: () => dataQuery(queryKey),
queryFn: () => sleep(20).then(() => queryKey),
})
return (
<div>
Expand All @@ -197,20 +196,23 @@ describe('React hydration', () => {
</QueryClientProvider>,
)

await rendered.findByText('string')
await vi.advanceTimersByTimeAsync(1)
expect(rendered.getByText('stringCached')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(20)
expect(rendered.getByText('string')).toBeInTheDocument()

const intermediateCache = new QueryCache()
const intermediateClient = createQueryClient({
queryCache: intermediateCache,
})
await intermediateClient.prefetchQuery({
const intermediateClient = createQueryClient()
intermediateClient.prefetchQuery({
queryKey: ['string'],
queryFn: () => dataQuery(['should not change']),
queryFn: () => sleep(20).then(() => ['should not change']),
})
await intermediateClient.prefetchQuery({
await vi.advanceTimersByTimeAsync(20)
intermediateClient.prefetchQuery({
queryKey: ['added'],
queryFn: () => dataQuery(['added']),
queryFn: () => sleep(20).then(() => ['added']),
})
await vi.advanceTimersByTimeAsync(20)

const newDehydratedState = dehydrate(intermediateClient)
intermediateClient.clear()

Expand Down Expand Up @@ -250,31 +252,30 @@ describe('React hydration', () => {
)

// This query existed before the transition so it should stay the same
rendered.getByText('string')
expect(rendered.getByText('string')).toBeInTheDocument()
expect(rendered.queryByText('should not change')).toBeNull()
// New query data should be available immediately because it was
// hydrated in the previous transition, even though the new dehydrated
// state did not contain it
rendered.getByText('added')
expect(rendered.getByText('added')).toBeInTheDocument()
})

await sleep(10)
await vi.advanceTimersByTimeAsync(20)
// It should stay the same even after effects have had a chance to run
rendered.getByText('string')
expect(rendered.getByText('string')).toBeInTheDocument()
expect(rendered.queryByText('should not change')).toBeNull()

queryClient.clear()
})

test('should hydrate queries to new cache if cache changes', async () => {
const dehydratedState = JSON.parse(stringifiedState)
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
const queryClient = createQueryClient()

function Page() {
const { data } = useQuery({
queryKey: ['string'],
queryFn: () => dataQuery(['string']),
queryFn: () => sleep(20).then(() => ['string']),
})
return (
<div>
Expand All @@ -291,12 +292,11 @@ describe('React hydration', () => {
</QueryClientProvider>,
)

await rendered.findByText('string')

const newClientQueryCache = new QueryCache()
const newClientQueryClient = createQueryClient({
queryCache: newClientQueryCache,
})
await vi.advanceTimersByTimeAsync(1)
expect(rendered.getByText('stringCached')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(20)
expect(rendered.getByText('string')).toBeInTheDocument()
const newClientQueryClient = createQueryClient()

rendered.rerender(
<QueryClientProvider client={newClientQueryClient}>
Expand All @@ -306,17 +306,16 @@ describe('React hydration', () => {
</QueryClientProvider>,
)

await sleep(10)
rendered.getByText('string')
await vi.advanceTimersByTimeAsync(20)
expect(rendered.getByText('string')).toBeInTheDocument()

queryClient.clear()
newClientQueryClient.clear()
})
})

test('should not hydrate queries if state is null', async () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
const queryClient = createQueryClient()

const hydrateSpy = vi.spyOn(coreModule, 'hydrate')

Expand All @@ -332,15 +331,19 @@ describe('React hydration', () => {
</QueryClientProvider>,
)

expect(hydrateSpy).toHaveBeenCalledTimes(0)
await Promise.all(
Array.from({ length: 1000 }).map(async (_, index) => {
await vi.advanceTimersByTimeAsync(index)
expect(hydrateSpy).toHaveBeenCalledTimes(0)
}),
)

hydrateSpy.mockRestore()
queryClient.clear()
})

test('should not hydrate queries if state is undefined', async () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
const queryClient = createQueryClient()

const hydrateSpy = vi.spyOn(coreModule, 'hydrate')

Expand All @@ -356,6 +359,7 @@ describe('React hydration', () => {
</QueryClientProvider>,
)

await vi.runAllTimersAsync()
expect(hydrateSpy).toHaveBeenCalledTimes(0)

hydrateSpy.mockRestore()
Expand Down
Loading