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: try to fix flakiness around offline mocks #7061

Merged
merged 3 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 43 additions & 62 deletions packages/react-query/src/__tests__/useMutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,6 @@ describe('useMutation', () => {

const rendered = renderWithClient(queryClient, <Page />)

window.dispatchEvent(new Event('offline'))

await waitFor(() => {
expect(
rendered.getByText('error: null, status: idle, isPaused: false'),
Expand All @@ -471,8 +469,8 @@ describe('useMutation', () => {

expect(count).toBe(0)

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getMutationCache().resumePausedMutations()

await sleep(100)

Expand All @@ -483,6 +481,7 @@ describe('useMutation', () => {
})

expect(count).toBe(2)
onlineMock.mockRestore()
})

it('should call onMutate even if paused', async () => {
Expand Down Expand Up @@ -515,22 +514,22 @@ describe('useMutation', () => {

await rendered.findByText('data: null, status: idle, isPaused: false')

window.dispatchEvent(new Event('offline'))

fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))

await rendered.findByText('data: null, status: pending, isPaused: true')

expect(onMutate).toHaveBeenCalledTimes(1)
expect(onMutate).toHaveBeenCalledWith('todo')

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getMutationCache().resumePausedMutations()

await rendered.findByText('data: 1, status: success, isPaused: false')

expect(onMutate).toHaveBeenCalledTimes(1)
expect(count).toBe(1)

onlineMock.mockRestore()
})

it('should optimistically go to paused state if offline', async () => {
Expand Down Expand Up @@ -564,8 +563,6 @@ describe('useMutation', () => {

await rendered.findByText('data: null, status: idle, isPaused: false')

window.dispatchEvent(new Event('offline'))

fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))

await rendered.findByText('data: null, status: pending, isPaused: true')
Expand All @@ -574,22 +571,25 @@ describe('useMutation', () => {
expect(states[0]).toBe('idle, false')
expect(states[1]).toBe('pending, true')

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getMutationCache().resumePausedMutations()

await rendered.findByText('data: 1, status: success, isPaused: false')

onlineMock.mockRestore()
})

it('should be able to retry a mutation when online', async () => {
const onlineMock = mockOnlineManagerIsOnline(false)
const key = queryKey()

let count = 0
const states: Array<UseMutationResult<any, any, any, any>> = []

function Page() {
const state = useMutation({
mutationKey: key,
mutationFn: async (_text: string) => {
await sleep(1)
await sleep(10)
count++
return count > 1
? Promise.resolve('data')
Expand All @@ -600,69 +600,50 @@ describe('useMutation', () => {
networkMode: 'offlineFirst',
})

states.push(state)

const { mutate } = state

React.useEffect(() => {
setActTimeout(() => {
window.dispatchEvent(new Event('offline'))
mutate('todo')
}, 10)
}, [mutate])

return null
return (
<div>
<button onClick={() => state.mutate('todo')}>mutate</button>
<div>status: {state.status}</div>
<div>isPaused: {String(state.isPaused)}</div>
<div>data: {state.data ?? 'null'}</div>
</div>
)
}

renderWithClient(queryClient, <Page />)

await sleep(50)
const rendered = renderWithClient(queryClient, <Page />)

expect(states.length).toBe(4)
expect(states[0]).toMatchObject({
isPending: false,
isPaused: false,
failureCount: 0,
failureReason: null,
})
expect(states[1]).toMatchObject({
isPending: true,
isPaused: false,
failureCount: 0,
failureReason: null,
})
expect(states[2]).toMatchObject({
isPending: true,
isPaused: false,
failureCount: 1,
failureReason: new Error('oops'),
})
expect(states[3]).toMatchObject({
isPending: true,
await waitFor(() => rendered.getByText('status: idle'))
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
await waitFor(() => rendered.getByText('isPaused: true'))

expect(
queryClient.getMutationCache().findAll({ mutationKey: key }).length,
).toBe(1)
expect(
queryClient.getMutationCache().findAll({ mutationKey: key })[0]?.state,
).toMatchObject({
status: 'pending',
isPaused: true,
failureCount: 1,
failureReason: new Error('oops'),
})

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getMutationCache().resumePausedMutations()

await sleep(50)
await waitFor(() => rendered.getByText('data: data'))

expect(states.length).toBe(6)
expect(states[4]).toMatchObject({
isPending: true,
isPaused: false,
failureCount: 1,
failureReason: new Error('oops'),
})
expect(states[5]).toMatchObject({
isPending: false,
expect(
queryClient.getMutationCache().findAll({ mutationKey: key })[0]?.state,
).toMatchObject({
status: 'success',
isPaused: false,
failureCount: 0,
failureReason: null,
data: 'data',
})

onlineMock.mockRestore()
})

it('should not change state if unmounted', async () => {
Expand Down
44 changes: 21 additions & 23 deletions packages/react-query/src/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5216,12 +5216,11 @@ describe('useQuery', () => {
}

const rendered = renderWithClient(queryClient, <Page />)
window.dispatchEvent(new Event('offline'))

await waitFor(() => rendered.getByText('status: pending, isPaused: true'))

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getQueryCache().onOnline()

await waitFor(() =>
rendered.getByText('status: success, isPaused: false'),
Expand All @@ -5231,6 +5230,7 @@ describe('useQuery', () => {
})

expect(states).toEqual(['paused', 'fetching', 'idle'])
onlineMock.mockRestore()
})

it('online queries should not refetch if you are offline', async () => {
Expand Down Expand Up @@ -5269,7 +5269,6 @@ describe('useQuery', () => {
await waitFor(() => rendered.getByText('data: data1'))

const onlineMock = mockOnlineManagerIsOnline(false)
window.dispatchEvent(new Event('offline'))

fireEvent.click(rendered.getByRole('button', { name: /invalidate/i }))

Expand All @@ -5280,8 +5279,8 @@ describe('useQuery', () => {
)
await waitFor(() => rendered.getByText('failureReason: null'))

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getQueryCache().onOnline()

await waitFor(() =>
rendered.getByText(
Expand All @@ -5299,6 +5298,8 @@ describe('useQuery', () => {
await waitFor(() => {
expect(rendered.getByText('data: data2')).toBeInTheDocument()
})

onlineMock.mockRestore()
})

it('online queries should not refetch if you are offline and refocus', async () => {
Expand Down Expand Up @@ -5488,7 +5489,6 @@ describe('useQuery', () => {
const onlineMock = mockOnlineManagerIsOnline(false)

const rendered = renderWithClient(queryClient, <Page />)
window.dispatchEvent(new Event('offline'))

await waitFor(() =>
rendered.getByText('status: success, fetchStatus: paused'),
Expand All @@ -5511,10 +5511,8 @@ describe('useQuery', () => {
window.dispatchEvent(new Event('visibilitychange'))
})

onlineMock.mockRestore()
act(() => {
window.dispatchEvent(new Event('online'))
})
onlineMock.mockReturnValue(true)
queryClient.getQueryCache().onOnline()

await waitFor(() =>
rendered.getByText('status: success, fetchStatus: idle'),
Expand All @@ -5524,6 +5522,8 @@ describe('useQuery', () => {
})

expect(count).toBe(1)

onlineMock.mockRestore()
})

it('online queries should pause retries if you are offline', async () => {
Expand Down Expand Up @@ -5560,7 +5560,6 @@ describe('useQuery', () => {
)

const onlineMock = mockOnlineManagerIsOnline(false)
window.dispatchEvent(new Event('offline'))

await sleep(20)

Expand All @@ -5574,7 +5573,7 @@ describe('useQuery', () => {
expect(count).toBe(1)

onlineMock.mockReturnValue(true)
window.dispatchEvent(new Event('online'))
queryClient.getQueryCache().onOnline()

await waitFor(() =>
rendered.getByText('status: error, fetchStatus: idle, failureCount: 3'),
Expand Down Expand Up @@ -5625,16 +5624,14 @@ describe('useQuery', () => {

const rendered = renderWithClient(queryClient, <Page />)

window.dispatchEvent(new Event('offline'))

await waitFor(() =>
rendered.getByText('status: pending, fetchStatus: paused'),
)

fireEvent.click(rendered.getByRole('button', { name: /hide/i }))

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getQueryCache().onOnline()

await waitFor(() => {
expect(queryClient.getQueryState(key)).toMatchObject({
Expand All @@ -5646,6 +5643,8 @@ describe('useQuery', () => {
// give it a bit more time to make sure queryFn is not called again
await sleep(15)
expect(count).toBe(1)

onlineMock.mockRestore()
})

it('online queries should not fetch if paused and we go online when cancelled and no refetchOnReconnect', async () => {
Expand Down Expand Up @@ -5695,7 +5694,7 @@ describe('useQuery', () => {
expect(count).toBe(0)

onlineMock.mockReturnValue(true)
window.dispatchEvent(new Event('online'))
queryClient.getQueryCache().onOnline()

await sleep(15)

Expand Down Expand Up @@ -5767,7 +5766,7 @@ describe('useQuery', () => {
await sleep(15)

onlineMock.mockReturnValue(true)
window.dispatchEvent(new Event('online'))
queryClient.getQueryCache().onOnline()

await sleep(15)

Expand Down Expand Up @@ -5901,8 +5900,6 @@ describe('useQuery', () => {

const rendered = renderWithClient(queryClient, <Page />)

window.dispatchEvent(new Event('offline'))

await waitFor(() =>
rendered.getByText(
'status: pending, fetchStatus: paused, failureCount: 1',
Expand All @@ -5912,15 +5909,16 @@ describe('useQuery', () => {

expect(count).toBe(1)

onlineMock.mockRestore()
window.dispatchEvent(new Event('online'))
onlineMock.mockReturnValue(true)
queryClient.getQueryCache().onOnline()

await waitFor(() =>
rendered.getByText('status: error, fetchStatus: idle, failureCount: 3'),
)
await waitFor(() => rendered.getByText('failureReason: failed3'))

expect(count).toBe(3)
onlineMock.mockRestore()
})
})

Expand Down
6 changes: 3 additions & 3 deletions packages/react-query/src/__tests__/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { act, render } from '@testing-library/react'
import * as utils from '@tanstack/query-core'
import { QueryClient, QueryClientProvider, onlineManager } from '..'
import type { QueryClientConfig } from '..'
import type { SpyInstance } from 'vitest'
import type { MockInstance } from 'vitest'

export function renderWithClient(
client: QueryClient,
Expand Down Expand Up @@ -48,13 +48,13 @@ export function createQueryClient(config?: QueryClientConfig): QueryClient {

export function mockVisibilityState(
value: DocumentVisibilityState,
): SpyInstance<[], DocumentVisibilityState> {
): MockInstance<[], DocumentVisibilityState> {
return vi.spyOn(document, 'visibilityState', 'get').mockReturnValue(value)
}

export function mockOnlineManagerIsOnline(
value: boolean,
): SpyInstance<[], boolean> {
): MockInstance<[], boolean> {
return vi.spyOn(onlineManager, 'isOnline').mockReturnValue(value)
}

Expand Down
Loading