import { fireEvent, waitFor } from '@testing-library/react' import { ErrorBoundary } from 'react-error-boundary' import * as React from 'react' import { createQueryClient, queryKey, renderWithClient, sleep } from './utils' import { QueryCache, QueryErrorResetBoundary, useQueries, useQuery } from '..' // TODO: This should be removed with the types for react-error-boundary get updated. declare module 'react-error-boundary' { interface ErrorBoundaryPropsWithFallback { children: any } } describe('QueryErrorResetBoundary', () => { const queryCache = new QueryCache() const queryClient = createQueryClient({ queryCache }) describe('useQuery', () => { it('should retry fetch if the reset error boundary has been reset', async () => { const key = queryKey() let succeed = false function Page() { const { data } = useQuery( key, async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, { retry: false, useErrorBoundary: true, }, ) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('data')) }) it('should not throw error if query is disabled', async () => { const key = queryKey() let succeed = false function Page() { const { data, status } = useQuery( key, async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, { retry: false, enabled: !succeed, useErrorBoundary: true, }, ) return (
status: {status}
{data}
) } const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('status: error')) }) it('should not throw error if query is disabled, and refetch if query becomes enabled again', async () => { const key = queryKey() let succeed = false function Page() { const [enabled, setEnabled] = React.useState(false) const { data } = useQuery( key, async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, { retry: false, enabled, useErrorBoundary: true, }, ) React.useEffect(() => { setEnabled(true) }, []) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('data')) }) it('should throw error if query is disabled and manually refetched', async () => { const key = queryKey() function Page() { const { data, refetch, status, fetchStatus } = useQuery( key, async () => { throw new Error('Error') }, { retry: false, enabled: false, useErrorBoundary: true, }, ) return (
status: {status}, fetchStatus: {fetchStatus}
{data}
) } const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('status: loading, fetchStatus: idle'), ) fireEvent.click(rendered.getByRole('button', { name: /refetch/i })) await waitFor(() => rendered.getByText('error boundary')) }) it('should not retry fetch if the reset error boundary has not been reset', async () => { const key = queryKey() let succeed = false function Page() { const { data } = useQuery( key, async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, { retry: false, useErrorBoundary: true, }, ) return
{data}
} const rendered = renderWithClient( queryClient, {() => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('error boundary')) }) it('should retry fetch if the reset error boundary has been reset and the query contains data from a previous fetch', async () => { const key = queryKey() let succeed = false function Page() { const { data } = useQuery( key, async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, { retry: false, useErrorBoundary: true, initialData: 'initial', }, ) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('data')) }) it('should not retry fetch if the reset error boundary has not been reset after a previous reset', async () => { const key = queryKey() let succeed = false let shouldReset = true function Page() { const { data } = useQuery( key, async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, { retry: false, useErrorBoundary: true, }, ) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( { if (shouldReset) { reset() } }} fallbackRender={({ resetErrorBoundary }) => (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) shouldReset = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('error boundary')) succeed = true shouldReset = false fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('error boundary')) }) it('should throw again on error after the reset error boundary has been reset', async () => { const key = queryKey() let fetchCount = 0 function Page() { const { data } = useQuery( key, async () => { fetchCount++ await sleep(10) throw new Error('Error') }, { retry: false, useErrorBoundary: true, }, ) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('error boundary')) expect(fetchCount).toBe(3) }) it('should never render the component while the query is in error state', async () => { const key = queryKey() let fetchCount = 0 let renders = 0 function Page() { const { data } = useQuery( key, async () => { fetchCount++ await sleep(10) if (fetchCount > 2) { return 'data' } else { throw new Error('Error') } }, { retry: false, suspense: true, }, ) renders++ return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} > loading}>
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('data')) expect(fetchCount).toBe(3) expect(renders).toBe(1) }) it('should render children', async () => { function Page() { return (
page
) } const rendered = renderWithClient( queryClient, , ) expect(rendered.queryByText('page')).not.toBeNull() }) it('should show error boundary when using tracked queries even though we do not track the error field', async () => { const key = queryKey() let succeed = false function Page() { const { data } = useQuery( key, async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, { retry: false, useErrorBoundary: true, }, ) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('data')) }) }) describe('useQueries', () => { it('should retry fetch if the reset error boundary has been reset', async () => { const key = queryKey() let succeed = false const queryOptions = { queryKey: key, queryFn: async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, retry: false, useErrorBoundary: true, retryOnMount: true, } function Page() { const [{ data }] = useQueries({ queries: [queryOptions] }) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('data')) }) it('with suspense should retry fetch if the reset error boundary has been reset', async () => { const key = queryKey() let succeed = false const queryOptions = { queryKey: key, queryFn: async () => { await sleep(10) if (!succeed) { throw new Error('Error') } else { return 'data' } }, retry: false, useErrorBoundary: true, retryOnMount: true, suspense: true, } function Page() { const [{ data }] = useQueries({ queries: [queryOptions] }) return
{data}
} const rendered = renderWithClient( queryClient, {({ reset }) => ( (
error boundary
)} >
)}
, ) await waitFor(() => rendered.getByText('error boundary')) await waitFor(() => rendered.getByText('retry')) succeed = true fireEvent.click(rendered.getByText('retry')) await waitFor(() => rendered.getByText('data')) }) }) })