import * as React from 'react'
import { fireEvent, screen, waitFor, act } from '@testing-library/react'
import { ErrorBoundary } from 'react-error-boundary'
import '@testing-library/jest-dom'
import type { QueryClient } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { defaultPanelSize, sortFns } from '../utils'
import {
getByTextContent,
renderWithClient,
sleep,
createQueryClient,
} from './utils'
import UserEvent from '@testing-library/user-event'
// TODO: This should be removed with the types for react-error-boundary get updated.
declare module 'react-error-boundary' {
interface ErrorBoundaryPropsWithFallback {
children: any
}
}
class CustomError extends Error {
constructor(message: string) {
super(message)
this.name = 'CustomError'
}
}
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
})
describe('ReactQueryDevtools', () => {
beforeEach(() => {
localStorage.removeItem('reactQueryDevtoolsOpen')
localStorage.removeItem('reactQueryDevtoolsPanelPosition')
})
it('should be able to open and close devtools', async () => {
const { queryClient } = createQueryClient()
const onCloseClick = jest.fn()
const onToggleClick = jest.fn()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => {
await sleep(10)
return 'test'
})
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: false,
closeButtonProps: { onClick: onCloseClick },
toggleButtonProps: { onClick: onToggleClick },
})
const verifyDevtoolsIsOpen = () => {
expect(
screen.queryByRole('generic', { name: /react query devtools panel/i }),
).not.toBeNull()
expect(
screen.queryByRole('button', { name: /open react query devtools/i }),
).toBeNull()
}
const verifyDevtoolsIsClosed = () => {
expect(
screen.queryByRole('generic', { name: /react query devtools panel/i }),
).toBeNull()
expect(
screen.queryByRole('button', { name: /open react query devtools/i }),
).not.toBeNull()
}
const waitForDevtoolsToOpen = () =>
screen.findByRole('button', { name: /close react query devtools/i })
const waitForDevtoolsToClose = () =>
screen.findByRole('button', { name: /open react query devtools/i })
const getOpenLogoButton = () =>
screen.getByRole('button', { name: /open react query devtools/i })
const getCloseLogoButton = () =>
screen.getByRole('button', { name: /close react query devtools/i })
const getCloseButton = () =>
screen.getByRole('button', { name: /^close$/i })
verifyDevtoolsIsClosed()
fireEvent.click(getOpenLogoButton())
await waitForDevtoolsToOpen()
verifyDevtoolsIsOpen()
fireEvent.click(getCloseLogoButton())
await waitForDevtoolsToClose()
verifyDevtoolsIsClosed()
fireEvent.click(getOpenLogoButton())
await waitForDevtoolsToOpen()
verifyDevtoolsIsOpen()
fireEvent.click(getCloseButton())
await waitForDevtoolsToClose()
verifyDevtoolsIsClosed()
})
it('should be able to drag devtools without error', async () => {
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => {
await sleep(10)
return 'test'
})
return (
{data}
)
}
const result = renderWithClient(queryClient, , {
initialIsOpen: false,
})
const draggableElement = result.container
.querySelector('#ReactQueryDevtoolsPanel')
?.querySelector('div')
if (!draggableElement) {
throw new Error('Could not find the draggable element')
}
await act(async () => {
fireEvent.mouseDown(draggableElement)
})
})
it('should display the correct query states', async () => {
const { queryClient, queryCache } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(
['check'],
async () => {
await sleep(100)
return 'test'
},
{ staleTime: 300 },
)
return (
{data}
)
}
function PageParent() {
const [isPageVisible, togglePageVisible] = React.useReducer(
(visible) => !visible,
true,
)
return (
Toggle Page
{isPageVisible &&
}
)
}
renderWithClient(queryClient, )
fireEvent.click(
screen.getByRole('button', { name: /open react query devtools/i }),
)
const currentQuery = queryCache.find(['check'])
// When the query is fetching then expect number of
// fetching queries to be 1
expect(currentQuery?.state.fetchStatus).toEqual('fetching')
await screen.findByText(
getByTextContent(
'fresh (0) fetching (1) paused (0) stale (0) inactive (0)',
),
)
// When we are done fetching the query doesn't go stale
// until 300ms after, so expect the number of fresh
// queries to be 1
await waitFor(() => {
expect(currentQuery?.state.fetchStatus).toEqual('idle')
})
await screen.findByText(
getByTextContent(
'fresh (1) fetching (0) paused (0) stale (0) inactive (0)',
),
)
// Then wait for the query to go stale and then
// expect the number of stale queries to be 1
await waitFor(() => {
expect(currentQuery?.isStale()).toEqual(false)
})
await screen.findByText(
getByTextContent(
'fresh (0) fetching (0) paused (0) stale (1) inactive (0)',
),
)
// Unmount the page component thus making the query inactive
// and expect number of inactive queries to be 1
fireEvent.click(
screen.getByRole('button', { name: /toggle page visibility/i }),
)
await screen.findByText(
getByTextContent(
'fresh (0) fetching (0) paused (0) stale (0) inactive (1)',
),
)
})
it('should display the query hash and open the query details', async () => {
const { queryClient, queryCache } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => {
await sleep(10)
return 'test'
})
return (
{data}
)
}
renderWithClient(queryClient, )
fireEvent.click(
screen.getByRole('button', { name: /open react query devtools/i }),
)
const currentQuery = queryCache.find(['check'])
await screen.findByText(getByTextContent(`1${currentQuery?.queryHash}`))
const queryButton = await screen.findByRole('button', {
name: `Open query details for ${currentQuery?.queryHash}`,
})
fireEvent.click(queryButton)
await screen.findByText(/query details/i)
})
it('should filter the queries via the query hash', async () => {
const { queryClient, queryCache } = createQueryClient()
function Page() {
const fooResult = useQuery(['foo'], async () => {
await sleep(10)
return 'foo-result'
})
const barResult = useQuery(['bar'], async () => {
await sleep(10)
return 'bar-result'
})
const bazResult = useQuery(['baz'], async () => {
await sleep(10)
return 'baz-result'
})
return (
{barResult.data} {fooResult.data} {bazResult.data}
)
}
renderWithClient(queryClient, )
fireEvent.click(
screen.getByRole('button', { name: /open react query devtools/i }),
)
const fooQueryHash = queryCache.find(['foo'])?.queryHash ?? 'invalid hash'
const barQueryHash = queryCache.find(['bar'])?.queryHash ?? 'invalid hash'
const bazQueryHash = queryCache.find(['baz'])?.queryHash ?? 'invalid hash'
await screen.findByText(fooQueryHash)
screen.getByText(barQueryHash)
screen.getByText(bazQueryHash)
const filterInput = screen.getByLabelText(/filter by queryhash/i)
fireEvent.change(filterInput, { target: { value: 'fo' } })
await screen.findByText(fooQueryHash)
const barItem = screen.queryByText(barQueryHash)
const bazItem = screen.queryByText(bazQueryHash)
expect(barItem).toBeNull()
expect(bazItem).toBeNull()
fireEvent.change(filterInput, { target: { value: '' } })
})
it('should show a disabled label if all observers are disabled', async () => {
const { queryClient } = createQueryClient()
function Page() {
const [enabled, setEnabled] = React.useState(false)
const { data } = useQuery(
['key'],
async () => {
await sleep(10)
return 'test'
},
{
enabled,
},
)
return (
{data}
setEnabled(true)}>Enable Query
)
}
renderWithClient(queryClient, , { initialIsOpen: true })
await screen.findByText(/disabled/i)
fireEvent.click(screen.getByRole('button', { name: /enable query/i }))
await waitFor(() => {
expect(screen.queryByText(/disabled/i)).not.toBeInTheDocument()
})
})
it('should not show a disabled label for inactive queries', async () => {
const { queryClient } = createQueryClient()
function Page() {
const { data } = useQuery(['key'], () => Promise.resolve('test'), {
enabled: false,
})
return (
{data}
)
}
function App() {
const [visible, setVisible] = React.useState(true)
return (
{visible ?
: null}
setVisible(false)}>Hide Query
)
}
renderWithClient(queryClient, , { initialIsOpen: true })
await screen.findByText(/disabled/i)
fireEvent.click(screen.getByRole('button', { name: /hide query/i }))
await waitFor(() => {
expect(screen.queryByText(/disabled/i)).not.toBeInTheDocument()
})
})
it('should simulate offline mode', async () => {
const { queryClient } = createQueryClient()
let count = 0
function App() {
const { data, fetchStatus } = useQuery(['key'], () => {
count++
return Promise.resolve('test')
})
return (
{data}, {fetchStatus}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
await screen.findByRole('heading', { name: /test/i })
fireEvent.click(
screen.getByRole('button', { name: /mock offline behavior/i }),
)
const queryButton = await screen.findByRole('button', {
name: 'Open query details for ["key"]',
})
fireEvent.click(queryButton)
const refetchButton = await screen.findByRole('button', {
name: /refetch/i,
})
fireEvent.click(refetchButton)
await waitFor(() => {
expect(screen.getByText('test, paused')).toBeInTheDocument()
})
fireEvent.click(
screen.getByRole('button', { name: /restore offline mock/i }),
)
await waitFor(() => {
expect(screen.getByText('test, idle')).toBeInTheDocument()
})
expect(count).toBe(2)
})
it('should sort the queries according to the sorting filter', async () => {
const { queryClient, queryCache } = createQueryClient()
function Page() {
const query1Result = useQuery(['query-1'], async () => {
await sleep(20)
return 'query-1-result'
})
const query3Result = useQuery(
['query-3'],
async () => {
await sleep(10)
return 'query-3-result'
},
{ staleTime: Infinity, enabled: typeof query1Result.data === 'string' },
)
const query2Result = useQuery(
['query-2'],
async () => {
await sleep(10)
return 'query-2-result'
},
{
enabled: typeof query3Result.data === 'string',
},
)
return (
{query1Result.data} {query2Result.data} {query3Result.data}
)
}
renderWithClient(queryClient, )
fireEvent.click(
screen.getByRole('button', { name: /open react query devtools/i }),
)
const query1Hash = queryCache.find(['query-1'])?.queryHash ?? 'invalid hash'
const query2Hash = queryCache.find(['query-2'])?.queryHash ?? 'invalid hash'
const query3Hash = queryCache.find(['query-3'])?.queryHash ?? 'invalid hash'
const sortSelect = screen.getByLabelText(/sort queries/i)
let queries = []
// When sorted by query hash the queries get sorted according
// to just the number, with the order being -> query-1, query-2, query-3
fireEvent.change(sortSelect, { target: { value: 'Query Hash' } })
/** To check the order of the queries we can use regex to find
* all the row items in an array and then compare the items
* one by one in the order we expect it
* @reference https://github.com/testing-library/react-testing-library/issues/313#issuecomment-625294327
*/
queries = await screen.findAllByText(/\["query-[1-3]"\]/)
expect(queries[0]?.textContent).toEqual(query1Hash)
expect(queries[1]?.textContent).toEqual(query2Hash)
expect(queries[2]?.textContent).toEqual(query3Hash)
// Wait for the queries to be resolved
await screen.findByText(/query-1-result query-2-result query-3-result/i)
// When sorted by the last updated date the queries are sorted by the time
// they were updated and since the query-2 takes longest time to complete
// and query-1 the shortest, so the order is -> query-2, query-3, query-1
fireEvent.change(sortSelect, { target: { value: 'Last Updated' } })
queries = await screen.findAllByText(/\["query-[1-3]"\]/)
expect(queries[0]?.textContent).toEqual(query2Hash)
expect(queries[1]?.textContent).toEqual(query3Hash)
expect(queries[2]?.textContent).toEqual(query1Hash)
// When sorted by the status and then last updated date the queries
// query-3 takes precedence because its stale time being infinity, it
// always remains fresh, the rest of the queries are sorted by their last
// updated time, so the resulting order is -> query-3, query-2, query-1
fireEvent.change(sortSelect, {
target: { value: 'Status > Last Updated' },
})
queries = await screen.findAllByText(/\["query-[1-3]"\]/)
expect(queries[0]?.textContent).toEqual(query3Hash)
expect(queries[1]?.textContent).toEqual(query2Hash)
expect(queries[2]?.textContent).toEqual(query1Hash)
// Switch the order form ascending to descending and expect the
// query order to be reversed from previous state
fireEvent.click(screen.getByRole('button', { name: /⬆ asc/i }))
queries = await screen.findAllByText(/\["query-[1-3]"\]/)
expect(queries[0]?.textContent).toEqual(query1Hash)
expect(queries[1]?.textContent).toEqual(query2Hash)
expect(queries[2]?.textContent).toEqual(query3Hash)
})
it('should initialize filtering and sorting values with defaults when they are not stored in localstorage', () => {
localStorage.removeItem('reactQueryDevtoolsBaseSort')
localStorage.removeItem('reactQueryDevtoolsSortFn')
localStorage.removeItem('reactQueryDevtoolsFilter')
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => {
await sleep(10)
return 'test'
})
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
const filterInput: HTMLInputElement =
screen.getByLabelText(/Filter by queryhash/i)
expect(filterInput.value).toEqual('')
const sortCombobox: HTMLSelectElement =
screen.getByLabelText(/Sort queries/i)
expect(sortCombobox.value).toEqual(Object.keys(sortFns)[0])
expect(screen.getByRole('button', { name: /Asc/i })).toBeInTheDocument()
const detailsPanel = screen.queryByText(/Query Details/i)
expect(detailsPanel).not.toBeInTheDocument()
})
it('should initialize sorting values with ones stored in localstorage', async () => {
localStorage.setItem('reactQueryDevtoolsBaseSort', 'true')
localStorage.setItem(
'reactQueryDevtoolsSortFn',
JSON.stringify(Object.keys(sortFns)[1]),
)
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => {
await sleep(10)
return 'test'
})
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
const sortCombobox: HTMLSelectElement =
screen.getByLabelText(/Sort queries/i)
expect(sortCombobox.value).toEqual(Object.keys(sortFns)[1])
expect(screen.getByRole('button', { name: /Desc/i })).toBeInTheDocument()
})
it('should initialize filter value with one stored in localstorage', () => {
localStorage.setItem('reactQueryDevtoolsFilter', JSON.stringify('posts'))
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => {
await sleep(10)
return 'test'
})
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
const filterInput: HTMLInputElement =
screen.getByLabelText(/Filter by queryhash/i)
expect(filterInput.value).toEqual('posts')
})
it('should not show queries after clear', async () => {
const { queryClient, queryCache } = createQueryClient()
function Page() {
const query1Result = useQuery(['query-1'], async () => {
return 'query-1-result'
})
const query2Result = useQuery(['query-2'], async () => {
return 'query-2-result'
})
const query3Result = useQuery(['query-3'], async () => {
return 'query-3-result'
})
return (
{query1Result.data} {query2Result.data} {query3Result.data}{' '}
)
}
renderWithClient(queryClient, )
fireEvent.click(
screen.getByRole('button', { name: /open react query devtools/i }),
)
expect(queryCache.getAll()).toHaveLength(3)
const clearButton = screen.getByLabelText(/clear/i)
fireEvent.click(clearButton)
expect(queryCache.getAll()).toHaveLength(0)
})
it('style should have a nonce', async () => {
const { queryClient } = createQueryClient()
function Page() {
return
}
const { container } = renderWithClient(queryClient, , {
styleNonce: 'test-nonce',
initialIsOpen: false,
})
const styleTag = container.querySelector('style')
expect(styleTag).toHaveAttribute('nonce', 'test-nonce')
await screen.findByRole('button', { name: /react query devtools/i })
})
describe('with custom context', () => {
it('should render without error when the custom context aligns', async () => {
const context = React.createContext(undefined)
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => 'test', {
context,
})
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: false,
context,
})
await screen.findByRole('button', { name: /open react query devtools/i })
})
it('should render with error when the custom context is not passed to useQuery', async () => {
const consoleErrorMock = jest.spyOn(console, 'error')
consoleErrorMock.mockImplementation(() => undefined)
const context = React.createContext(undefined)
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => 'test', {
useErrorBoundary: true,
})
return (
{data}
)
}
const rendered = renderWithClient(
queryClient,
error boundary
}>
,
{
initialIsOpen: false,
context,
},
)
await waitFor(() => rendered.getByText('error boundary'))
consoleErrorMock.mockRestore()
})
it('should render with error when the custom context is not passed to ReactQueryDevtools', async () => {
const consoleErrorMock = jest.spyOn(console, 'error')
consoleErrorMock.mockImplementation(() => undefined)
const context = React.createContext(undefined)
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => 'test', {
useErrorBoundary: true,
context,
})
return (
{data}
)
}
const rendered = renderWithClient(
queryClient,
error boundary
}>
,
{
initialIsOpen: false,
},
)
await waitFor(() => rendered.getByText('error boundary'))
consoleErrorMock.mockRestore()
})
})
it('should render a menu to select panel position', async () => {
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => 'test')
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
const positionSelect = (await screen.findByLabelText(
'Panel position',
)) as HTMLSelectElement
expect(positionSelect.value).toBe('bottom')
})
it(`should render the panel to the left if panelPosition is set to 'left'`, async () => {
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => 'test')
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
panelPosition: 'left',
})
const positionSelect = (await screen.findByLabelText(
'Panel position',
)) as HTMLSelectElement
expect(positionSelect.value).toBe('left')
const panel = (await screen.getByLabelText(
'React Query Devtools Panel',
)) as HTMLDivElement
expect(panel.style.left).toBe('0px')
expect(panel.style.width).toBe('500px')
expect(panel.style.height).toBe('100vh')
})
it('should change the panel position if user select different option from the menu', async () => {
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => 'test')
return (
{data}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
const positionSelect = (await screen.findByLabelText(
'Panel position',
)) as HTMLSelectElement
expect(positionSelect.value).toBe('bottom')
const panel = (await screen.getByLabelText(
'React Query Devtools Panel',
)) as HTMLDivElement
expect(panel.style.bottom).toBe('0px')
expect(panel.style.height).toBe('500px')
expect(panel.style.width).toBe('100%')
await act(async () => {
fireEvent.change(positionSelect, { target: { value: 'right' } })
})
expect(positionSelect.value).toBe('right')
expect(panel.style.right).toBe('0px')
expect(panel.style.width).toBe('500px')
expect(panel.style.height).toBe('100vh')
})
it('should restore parent element padding after closing', async () => {
const { queryClient } = createQueryClient()
function Page() {
const { data = 'default' } = useQuery(['check'], async () => 'test')
return (
{data}
)
}
const parentElementTestid = 'parentElement'
const parentPaddings = {
paddingTop: '428px',
paddingBottom: '39px',
paddingLeft: '-373px',
paddingRight: '20%',
}
function Parent({ children }: { children: React.ReactElement }) {
return (
{children}
)
}
renderWithClient(
queryClient,
,
{
initialIsOpen: true,
panelPosition: 'bottom',
},
{ wrapper: Parent },
)
const parentElement = screen.getByTestId(parentElementTestid)
expect(parentElement).toHaveStyle({
paddingTop: '0px',
paddingLeft: '0px',
paddingRight: '0px',
paddingBottom: defaultPanelSize,
})
fireEvent.click(screen.getByRole('button', { name: /^close$/i }))
expect(parentElement).toHaveStyle(parentPaddings)
})
it('should simulate loading state', async () => {
const { queryClient } = createQueryClient()
let count = 0
function App() {
const { data, fetchStatus } = useQuery(['key'], () => {
count++
return Promise.resolve('test')
})
return (
{data ?? 'No data'}, {fetchStatus}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
await screen.findByRole('heading', { name: /test/i })
const loadingButton = await screen.findByRole('button', {
name: 'Trigger loading',
})
fireEvent.click(loadingButton)
await waitFor(() => {
expect(screen.getByText('Restore loading')).toBeInTheDocument()
})
await waitFor(() => {
expect(screen.getByText('No data, fetching')).toBeInTheDocument()
})
fireEvent.click(screen.getByRole('button', { name: /restore loading/i }))
await waitFor(() => {
expect(screen.getByText('test, idle')).toBeInTheDocument()
})
expect(count).toBe(2)
})
it('should simulate error state', async () => {
const { queryClient } = createQueryClient()
function App() {
const { status, error } = useQuery(['key'], () => {
return Promise.resolve('test')
})
return (
{!!error ? 'Some error' : 'No error'}, {status}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
const errorButton = await screen.findByRole('button', {
name: 'Trigger error',
})
fireEvent.click(errorButton)
await waitFor(() => {
expect(screen.getByText('Restore error')).toBeInTheDocument()
})
await waitFor(() => {
expect(screen.getByText('Some error, error')).toBeInTheDocument()
})
fireEvent.click(screen.getByRole('button', { name: /Restore error/i }))
await waitFor(() => {
expect(screen.getByText('No error, success')).toBeInTheDocument()
})
})
it('should can simulate a specific error', async () => {
const { queryClient } = createQueryClient()
function App() {
const { status, error } = useQuery(['key'], () => {
return Promise.resolve('test')
})
return (
{error instanceof CustomError
? error.message.toString()
: 'No error'}
, {status}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
errorTypes: [
{
name: 'error1',
initializer: () => new CustomError('error1'),
},
],
})
const errorOption = await screen.findByLabelText('Trigger error:')
UserEvent.selectOptions(errorOption, 'error1')
await waitFor(() => {
expect(screen.getByText('error1, error')).toBeInTheDocument()
})
fireEvent.click(screen.getByRole('button', { name: /Restore error/i }))
await waitFor(() => {
expect(screen.getByText('No error, success')).toBeInTheDocument()
})
})
it('should not refetch when already restoring a query', async () => {
const { queryClient } = createQueryClient()
let count = 0
let resolvePromise: (value: unknown) => void = () => undefined
function App() {
const { data } = useQuery(['key'], () => {
count++
// Resolve the promise immediately when
// the query is fetched for the first time
if (count === 1) {
return Promise.resolve('test')
}
return new Promise((resolve) => {
// Do not resolve immediately and store the
// resolve function to resolve the promise later
resolvePromise = resolve
})
})
return (
{typeof data === 'string' ? data : 'No data'}
)
}
renderWithClient(queryClient, , {
initialIsOpen: true,
})
const loadingButton = await screen.findByRole('button', {
name: 'Trigger loading',
})
fireEvent.click(loadingButton)
await waitFor(() => {
expect(screen.getByText('Restore loading')).toBeInTheDocument()
})
// Click the restore loading button twice and only resolve query promise
// after the second click.
fireEvent.click(screen.getByRole('button', { name: /restore loading/i }))
fireEvent.click(screen.getByRole('button', { name: /restore loading/i }))
resolvePromise('test')
expect(count).toBe(2)
})
})