Skip to content
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
4 changes: 1 addition & 3 deletions src/react/setLogger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { setLogger } from '../core'
import { logger } from './logger'

if (logger) {
setLogger(logger)
}
setLogger(logger)
47 changes: 47 additions & 0 deletions src/react/tests/Hydrate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Hydrate,
} from '../..'
import { sleep } from './utils'
import * as coreModule from '../../core/index'

describe('React hydration', () => {
const fetchData: (value: string) => Promise<string> = value =>
Expand Down Expand Up @@ -158,4 +159,50 @@ describe('React hydration', () => {
newClientQueryClient.clear()
})
})

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

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

function Page() {
useHydrate(null)
return null
}

render(
<QueryClientProvider client={queryClient}>
<Page />
</QueryClientProvider>
)

expect(hydrateSpy).toHaveBeenCalledTimes(0)

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

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

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

function Page() {
useHydrate(undefined)
return null
}

render(
<QueryClientProvider client={queryClient}>
<Page />
</QueryClientProvider>
)

expect(hydrateSpy).toHaveBeenCalledTimes(0)

hydrateSpy.mockRestore()
queryClient.clear()
})
})
82 changes: 81 additions & 1 deletion src/react/tests/QueryClientProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React from 'react'
import { render, waitFor } from '@testing-library/react'
import { renderToString } from 'react-dom/server'

import { sleep, queryKey } from './utils'
import { QueryClient, QueryClientProvider, QueryCache, useQuery } from '../..'
import {
QueryClient,
QueryClientProvider,
QueryCache,
useQuery,
useQueryClient,
} from '../..'

describe('QueryClientProvider', () => {
test('sets a specific cache for all queries to use', async () => {
Expand Down Expand Up @@ -127,4 +134,77 @@ describe('QueryClientProvider', () => {
expect(queryCache.find(key)).toBeDefined()
expect(queryCache.find(key)?.options.cacheTime).toBe(Infinity)
})

describe('useQueryClient', () => {
test('should throw an error if no query client has been set', () => {
const consoleMock = jest
.spyOn(console, 'error')
.mockImplementation(() => undefined)

function Page() {
useQueryClient()
return null
}

expect(() => render(<Page />)).toThrow(
'No QueryClient set, use QueryClientProvider to set one'
)

consoleMock.mockRestore()
})

test('should use window to get the context when contextSharing is true', () => {
const queryCache = new QueryCache()
const queryClient = new QueryClient({ queryCache })

let queryClientFromHook: QueryClient | undefined
let queryClientFromWindow: QueryClient | undefined

function Page() {
queryClientFromHook = useQueryClient()
queryClientFromWindow = React.useContext(
window.ReactQueryClientContext as React.Context<
QueryClient | undefined
>
)
return null
}

render(
<QueryClientProvider client={queryClient} contextSharing={true}>
<Page />
</QueryClientProvider>
)

expect(queryClientFromHook).toEqual(queryClient)
expect(queryClientFromWindow).toEqual(queryClient)
})

test('should not use window to get the context when contextSharing is true and window does not exist', () => {
const queryCache = new QueryCache()
const queryClient = new QueryClient({ queryCache })

// Mock a non web browser environment
const windowSpy = jest
.spyOn(window, 'window', 'get')
.mockImplementation(undefined)

let queryClientFromHook: QueryClient | undefined

function Page() {
queryClientFromHook = useQueryClient()
return null
}

renderToString(
<QueryClientProvider client={queryClient} contextSharing={true}>
<Page />
</QueryClientProvider>
)

expect(queryClientFromHook).toEqual(queryClient)

windowSpy.mockRestore()
})
})
})
19 changes: 19 additions & 0 deletions src/react/tests/QueryResetErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,23 @@ describe('QueryErrorResetBoundary', () => {

consoleMock.mockRestore()
})

it('should render children', async () => {
function Page() {
return (
<div>
<span>page</span>
</div>
)
}

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

expect(rendered.queryByText('page')).not.toBeNull()
})
})
9 changes: 9 additions & 0 deletions src/react/tests/logger.native.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { logger } from '../logger.native'

describe('logger native', () => {
it('should expose logger properties', () => {
expect(logger).toHaveProperty('log')
expect(logger).toHaveProperty('error')
expect(logger).toHaveProperty('warn')
})
})
55 changes: 54 additions & 1 deletion src/react/tests/useIsMutating.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { waitFor } from '@testing-library/react'
import { waitFor, fireEvent } from '@testing-library/react'
import React from 'react'
import { QueryClient } from '../../core'
import { useIsMutating } from '../useIsMutating'
import { useMutation } from '../useMutation'
import { renderWithClient, setActTimeout, sleep } from './utils'
import * as MutationCacheModule from '../../core/mutationCache'

describe('useIsMutating', () => {
it('should return the number of fetching mutations', async () => {
Expand Down Expand Up @@ -105,4 +106,56 @@ describe('useIsMutating', () => {
renderWithClient(queryClient, <Page />)
await waitFor(() => expect(isMutatings).toEqual([0, 1, 1, 1, 0, 0]))
})

it('should not change state if unmounted', async () => {
// We have to mock the MutationCache to not unsubscribe
// the listener when the component is unmounted
class MutationCacheMock extends MutationCacheModule.MutationCache {
subscribe(listener: any) {
super.subscribe(listener)
return () => void 0
}
}

const MutationCacheSpy = jest
.spyOn(MutationCacheModule, 'MutationCache')
.mockImplementation(fn => {
return new MutationCacheMock(fn)
})

const queryClient = new QueryClient()

function IsMutating() {
useIsMutating()
return null
}

function Page() {
const [mounted, setMounted] = React.useState(true)
const { mutate: mutate1 } = useMutation('mutation1', async () => {
await sleep(10)
return 'data'
})

React.useEffect(() => {
mutate1()
}, [mutate1])

return (
<div>
<button onClick={() => setMounted(false)}>unmount</button>
{mounted && <IsMutating />}
</div>
)
}

const { getByText } = renderWithClient(queryClient, <Page />)
fireEvent.click(getByText('unmount'))

// Should not display the console error
// "Warning: Can't perform a React state update on an unmounted component"

await sleep(20)
MutationCacheSpy.mockRestore()
})
})
43 changes: 43 additions & 0 deletions src/react/tests/useMutation.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fireEvent, waitFor } from '@testing-library/react'
import React from 'react'
import { ErrorBoundary } from 'react-error-boundary'

import { useMutation, QueryClient, QueryCache, MutationCache } from '../..'
import { UseMutationResult } from '../types'
Expand Down Expand Up @@ -476,4 +477,46 @@ describe('useMutation', () => {
fireEvent.click(getByText('mutate'))
fireEvent.click(getByText('unmount'))
})

it('should be able to throw an error when useErrorBoundary is set to true', async () => {
const consoleMock = mockConsoleError()

function Page() {
const { mutate } = useMutation<string, Error>(
() => {
const err = new Error('Expected mock error. All is well!')
err.stack = ''
return Promise.reject(err)
},
{ useErrorBoundary: true }
)

return (
<div>
<button onClick={() => mutate()}>mutate</button>
</div>
)
}

const { getByText, queryByText } = renderWithClient(
queryClient,
<ErrorBoundary
fallbackRender={() => (
<div>
<span>error</span>
</div>
)}
>
<Page />
</ErrorBoundary>
)

fireEvent.click(getByText('mutate'))

await waitFor(() => {
expect(queryByText('error')).not.toBeNull()
})

consoleMock.mockRestore()
})
})
62 changes: 61 additions & 1 deletion src/react/tests/useQueries.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { waitFor } from '@testing-library/react'
import { waitFor, fireEvent } from '@testing-library/react'
import React from 'react'

import * as QueriesObserverModule from '../../core/queriesObserver'

import {
expectType,
expectTypeNotAny,
Expand All @@ -15,6 +17,7 @@ import {
UseQueryResult,
QueryCache,
QueryObserverResult,
QueriesObserver,
} from '../..'

describe('useQueries', () => {
Expand Down Expand Up @@ -712,4 +715,61 @@ describe('useQueries', () => {
)
}
})

it('should not change state if unmounted', async () => {
const key1 = queryKey()

// We have to mock the QueriesObserver to not unsubscribe
// the listener when the component is unmounted
class QueriesObserverMock extends QueriesObserver {
subscribe(listener: any) {
super.subscribe(listener)
return () => void 0
}
}

const QueriesObserverSpy = jest
.spyOn(QueriesObserverModule, 'QueriesObserver')
.mockImplementation(fn => {
return new QueriesObserverMock(fn)
})

function Queries() {
useQueries([
{
queryKey: key1,
queryFn: async () => {
await sleep(10)
return 1
},
},
])

return (
<div>
<span>queries</span>
</div>
)
}

function Page() {
const [mounted, setMounted] = React.useState(true)

return (
<div>
<button onClick={() => setMounted(false)}>unmount</button>
{mounted && <Queries />}
</div>
)
}

const { getByText } = renderWithClient(queryClient, <Page />)
fireEvent.click(getByText('unmount'))

// Should not display the console error
// "Warning: Can't perform a React state update on an unmounted component"

await sleep(20)
QueriesObserverSpy.mockRestore()
})
})