import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import * as React from 'react' import { ErrorBoundary } from 'react-error-boundary' import { createRenderStream, useTrackRenders, } from '@testing-library/react-render-stream' import { QueryClientProvider, QueryErrorResetBoundary, keepPreviousData, useQuery, } from '..' import { QueryCache } from '../index' import { createQueryClient, queryKey, sleep } from './utils' describe('useQuery().promise', () => { const queryCache = new QueryCache() const queryClient = createQueryClient({ queryCache, }) beforeAll(() => { queryClient.setDefaultOptions({ queries: { experimental_prefetchInRender: true }, }) }) afterAll(() => { queryClient.setDefaultOptions({ queries: { experimental_prefetchInRender: false }, }) }) it('should work with a basic test', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) useTrackRenders() return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { useTrackRenders() const query = useQuery({ queryKey: key, queryFn: async () => { await sleep(1) return 'test' }, }) return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test') expect(renderedComponents).toEqual([Page, MyComponent]) } }) it('colocate suspense and promise', async () => { const key = queryKey() let callCount = 0 const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent() { useTrackRenders() const query = useQuery({ queryKey: key, queryFn: async () => { callCount++ await sleep(1) return 'test' }, staleTime: 1000, }) const data = React.use(query.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { useTrackRenders() return ( <React.Suspense fallback={<Loading />}> <MyComponent /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test') expect(renderedComponents).toEqual([MyComponent]) } expect(callCount).toBe(1) }) it('parallel queries', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) let callCount = 0 function MyComponent() { useTrackRenders() const query = useQuery({ queryKey: key, queryFn: async () => { callCount++ await sleep(1) return 'test' }, staleTime: 1000, }) const data = React.use(query.promise) return data } function Loading() { useTrackRenders() return <>loading..</> } function Page() { useTrackRenders() return ( <> <React.Suspense fallback={<Loading />}> <MyComponent /> <MyComponent /> <MyComponent /> </React.Suspense> <React.Suspense fallback={null}> <MyComponent /> <MyComponent /> </React.Suspense> </> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('testtesttesttesttest') expect(renderedComponents).toEqual([ MyComponent, MyComponent, MyComponent, MyComponent, MyComponent, ]) } expect(callCount).toBe(1) }) it('should work with initial data', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent(props: { promise: Promise<string> }) { useTrackRenders() const data = React.use(props.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { useTrackRenders() const query = useQuery({ queryKey: key, queryFn: async () => { await sleep(1) return 'test' }, initialData: 'initial', }) return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('initial') expect(renderedComponents).toEqual([Page, MyComponent]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test') expect(renderedComponents).toEqual([Page, MyComponent]) } }) it('should not fetch with initial data and staleTime', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) const queryFn = vi.fn().mockImplementation(async () => { await sleep(1) return 'test' }) function MyComponent(props: { promise: Promise<string> }) { useTrackRenders() const data = React.use(props.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { useTrackRenders() const query = useQuery({ queryKey: key, queryFn, initialData: 'initial', staleTime: 1000, }) return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('initial') expect(renderedComponents).toEqual([Page, MyComponent]) } // should not call queryFn because of staleTime + initialData combo expect(queryFn).toHaveBeenCalledTimes(0) }) it('should work with static placeholderData', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent(props: { promise: Promise<string> }) { useTrackRenders() const data = React.use(props.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { const query = useQuery({ queryKey: key, queryFn: async () => { await sleep(1) return 'test' }, placeholderData: 'placeholder', }) useTrackRenders() return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('placeholder') expect(renderedComponents).toEqual([Page, MyComponent]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test') expect(renderedComponents).toEqual([Page, MyComponent]) } }) it('should work with placeholderData: keepPreviousData', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent(props: { promise: Promise<string> }) { useTrackRenders() const data = React.use(props.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { useTrackRenders() const [count, setCount] = React.useState(0) const query = useQuery({ queryKey: [...key, count], queryFn: async () => { await sleep(1) return 'test-' + count }, placeholderData: keepPreviousData, }) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => setCount((c) => c + 1)}>increment</button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test-0') expect(renderedComponents).toEqual([MyComponent]) } rendered.getByRole('button', { name: 'increment' }).click() // re-render because of the increment { const { renderedComponents } = await renderStream.takeRender() expect(renderedComponents).toEqual([Page, MyComponent]) } // re-render with new data, no loading between { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test-1') // no more suspense boundary rendering expect(renderedComponents).toEqual([Page, MyComponent]) } }) it('should be possible to select a part of the data with select', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent(props: { promise: Promise<string> }) { useTrackRenders() const data = React.use(props.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { const query = useQuery({ queryKey: key, queryFn: async () => { await sleep(1) return { name: 'test' } }, select: (data) => data.name, }) useTrackRenders() return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test') expect(renderedComponents).toEqual([MyComponent]) } }) it('should throw error if the promise fails', async () => { const renderStream = createRenderStream({ snapshotDOM: true }) const consoleMock = vi .spyOn(console, 'error') .mockImplementation(() => undefined) const key = queryKey() function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) return <>{data}</> } function Loading() { return <>loading..</> } let queryCount = 0 function Page() { const query = useQuery({ queryKey: key, queryFn: async () => { await sleep(1) if (++queryCount > 1) { // second time this query mounts, it should not throw return 'data' } throw new Error('Error test') }, retry: false, }) return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <QueryErrorResetBoundary> {({ reset }) => ( <ErrorBoundary onReset={reset} fallbackRender={({ resetErrorBoundary }) => ( <div> <div>error boundary</div> <button onClick={() => { resetErrorBoundary() }} > resetErrorBoundary </button> </div> )} > <Page /> </ErrorBoundary> )} </QueryErrorResetBoundary> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('error boundary') } consoleMock.mockRestore() rendered.getByText('resetErrorBoundary').click() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('data') } expect(queryCount).toBe(2) }) it('should throw error if the promise fails (colocate suspense and promise)', async () => { const renderStream = createRenderStream({ snapshotDOM: true }) const consoleMock = vi .spyOn(console, 'error') .mockImplementation(() => undefined) const key = queryKey() function MyComponent() { const query = useQuery({ queryKey: key, queryFn: async () => { await sleep(1) throw new Error('Error test') }, retry: false, }) const data = React.use(query.promise) return <>{data}</> } function Page() { return ( <React.Suspense fallback="loading.."> <MyComponent /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <ErrorBoundary fallbackRender={() => <div>error boundary</div>}> <Page /> </ErrorBoundary> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('error boundary') } consoleMock.mockRestore() }) it('should recreate promise with data changes', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent(props: { promise: Promise<string> }) { useTrackRenders() const data = React.use(props.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { const query = useQuery({ queryKey: key, queryFn: async () => { await sleep(1) return 'test1' }, }) useTrackRenders() return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test1') expect(renderedComponents).toEqual([MyComponent]) } queryClient.setQueryData(key, 'test2') { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test2') expect(renderedComponents).toEqual([Page, MyComponent]) } }) it('should dedupe when re-fetched with queryClient.fetchQuery while suspending', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) const queryFn = vi.fn().mockImplementation(async () => { await sleep(10) return 'test' }) const options = { queryKey: key, queryFn, } function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const query = useQuery(options) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => queryClient.fetchQuery(options)}>fetch</button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } rendered.getByText('fetch').click() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('test') } expect(queryFn).toHaveBeenCalledOnce() }) it('should dedupe when re-fetched with refetchQueries while suspending', async () => { const key = queryKey() let count = 0 const renderStream = createRenderStream({ snapshotDOM: true }) const queryFn = vi.fn().mockImplementation(async () => { await sleep(10) return 'test' + count++ }) const options = { queryKey: key, queryFn, } function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const query = useQuery(options) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => queryClient.refetchQueries(options)}> refetch </button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } rendered.getByText('refetch').click() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('test0') } expect(queryFn).toHaveBeenCalledOnce() }) it('should stay pending when canceled with cancelQueries while suspending until refetched', async () => { const renderStream = createRenderStream({ snapshotDOM: true }) const key = queryKey() let count = 0 const queryFn = vi.fn().mockImplementation(async () => { await sleep(10) return 'test' + count++ }) const options = { queryKey: key, queryFn, } function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const query = useQuery(options) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => queryClient.cancelQueries(options)}> cancel </button> <button onClick={() => queryClient.setQueryData<string>(key, 'hello')} > fetch </button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <ErrorBoundary fallbackRender={() => <>error boundary</>}> <Page /> </ErrorBoundary> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } rendered.getByText('cancel').click() { await renderStream.takeRender() expect(queryClient.getQueryState(key)).toMatchObject({ status: 'pending', fetchStatus: 'idle', }) } expect(queryFn).toHaveBeenCalledOnce() rendered.getByText('fetch').click() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('hello') } }) it('should resolve to previous data when canceled with cancelQueries while suspending', async () => { const renderStream = createRenderStream({ snapshotDOM: true }) const key = queryKey() const queryFn = vi.fn().mockImplementation(async () => { await sleep(10) return 'test' }) const options = { queryKey: key, queryFn, } function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const query = useQuery(options) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => queryClient.cancelQueries(options)}> cancel </button> </div> ) } queryClient.setQueryData(key, 'initial') const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) rendered.getByText('cancel').click() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('initial') } expect(queryFn).toHaveBeenCalledTimes(1) }) it('should suspend when not enabled', async () => { const renderStream = createRenderStream({ snapshotDOM: true }) const key = queryKey() const options = (count: number) => ({ queryKey: [...key, count], queryFn: async () => { await sleep(10) return 'test' + count }, }) function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const [count, setCount] = React.useState(0) const query = useQuery({ ...options(count), enabled: count > 0 }) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => setCount(1)}>enable</button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } rendered.getByText('enable').click() // loading re-render with enabled await renderStream.takeRender() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('test1') } }) it('should show correct data when read from cache only (staleTime)', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) queryClient.setQueryData(key, 'initial') const queryFn = vi.fn().mockImplementation(async () => { await sleep(1) return 'test' }) function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const query = useQuery({ queryKey: key, queryFn, staleTime: Infinity, }) return ( <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> ) } await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('initial') } expect(queryFn).toHaveBeenCalledTimes(0) }) it('should show correct data when switching between cache entries without re-fetches', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent(props: { promise: Promise<string> }) { useTrackRenders() const data = React.use(props.promise) return <>{data}</> } function Loading() { useTrackRenders() return <>loading..</> } function Page() { useTrackRenders() const [count, setCount] = React.useState(0) const query = useQuery({ queryKey: [key, count], queryFn: async () => { await sleep(10) return 'test' + count }, staleTime: Infinity, }) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => setCount(count + 1)}>inc</button> <button onClick={() => setCount(count - 1)}>dec</button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test0') expect(renderedComponents).toEqual([MyComponent]) } rendered.getByText('inc').click() { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(renderedComponents).toEqual([Page, Loading]) } { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test1') expect(renderedComponents).toEqual([MyComponent]) } rendered.getByText('dec').click() { const { renderedComponents, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test0') expect(renderedComponents).toEqual([Page, MyComponent]) } }) it('should not resolve with intermediate data when keys are switched', async () => { const key = queryKey() const renderStream = createRenderStream<{ data: string }>({ snapshotDOM: true, }) function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) renderStream.replaceSnapshot({ data }) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const [count, setCount] = React.useState(0) const query = useQuery({ queryKey: [key, count], queryFn: async () => { await sleep(10) return 'test' + count }, staleTime: Infinity, }) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => setCount(count + 1)}>inc</button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } { const { snapshot, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test0') expect(snapshot).toMatchObject({ data: 'test0' }) } rendered.getByText('inc').click() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } rendered.getByText('inc').click() await renderStream.takeRender() rendered.getByText('inc').click() await renderStream.takeRender() { const { snapshot, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test3') expect(snapshot).toMatchObject({ data: 'test3' }) } }) it('should not resolve with intermediate data when keys are switched (with background updates)', async () => { const key = queryKey() const renderStream = createRenderStream<{ data: string }>({ snapshotDOM: true, }) let modifier = '' function MyComponent(props: { promise: Promise<string> }) { const data = React.use(props.promise) renderStream.replaceSnapshot({ data }) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const [count, setCount] = React.useState(0) const query = useQuery({ queryKey: [key, count], queryFn: async () => { await sleep(10) return 'test' + count + modifier }, }) return ( <div> <React.Suspense fallback={<Loading />}> <MyComponent promise={query.promise} /> </React.Suspense> <button onClick={() => setCount(count + 1)}>inc</button> <button onClick={() => setCount(count - 1)}>dec</button> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } { const { snapshot, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test0') expect(snapshot).toMatchObject({ data: 'test0' }) } rendered.getByText('inc').click() { const { snapshot } = await renderStream.takeRender() expect(snapshot).toMatchObject({ data: 'test0' }) } rendered.getByText('inc').click() { const { snapshot } = await renderStream.takeRender() expect(snapshot).toMatchObject({ data: 'test0' }) } rendered.getByText('inc').click() { const { snapshot, withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') expect(snapshot).toMatchObject({ data: 'test0' }) } { const { snapshot, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test3') expect(snapshot).toMatchObject({ data: 'test3' }) } modifier = 'new' rendered.getByText('dec').click() { const { snapshot } = await renderStream.takeRender() expect(snapshot).toMatchObject({ data: 'test2' }) } rendered.getByText('dec').click() { const { snapshot } = await renderStream.takeRender() expect(snapshot).toMatchObject({ data: 'test1' }) } rendered.getByText('dec').click() { const { snapshot } = await renderStream.takeRender() expect(snapshot).toMatchObject({ data: 'test0' }) } { const { snapshot, withinDOM } = await renderStream.takeRender() withinDOM().getByText('test0new') expect(snapshot).toMatchObject({ data: 'test0new' }) } }) it('should not suspend indefinitely with multiple, nested observers)', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) function MyComponent({ input }: { input: string }) { const query = useTheQuery(input) const data = React.use(query.promise) return <>{data}</> } function useTheQuery(input: string) { return useQuery({ staleTime: Infinity, queryKey: [key, input], queryFn: async () => { await sleep(1) return input + ' response' }, }) } function Page() { const [input, setInput] = React.useState('defaultInput') useTheQuery(input) return ( <div> <button onClick={() => setInput('someInput')}>setInput</button> <React.Suspense fallback="loading.."> <MyComponent input={input} /> </React.Suspense> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('defaultInput response') } expect( queryClient.getQueryCache().find({ queryKey: [key, 'defaultInput'] })! .observers.length, ).toBe(2) rendered.getByText('setInput').click() { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('loading..') } { const { withinDOM } = await renderStream.takeRender() withinDOM().getByText('someInput response') } expect( queryClient.getQueryCache().find({ queryKey: [key, 'defaultInput'] })! .observers.length, ).toBe(0) expect( queryClient.getQueryCache().find({ queryKey: [key, 'someInput'] })! .observers.length, ).toBe(2) }) it('should handle enabled state changes with suspense', async () => { const key = queryKey() const renderStream = createRenderStream({ snapshotDOM: true }) const queryFn = vi.fn(async () => { await sleep(1) return 'test' }) function MyComponent(props: { enabled: boolean }) { const query = useQuery({ queryKey: key, queryFn, enabled: props.enabled, staleTime: Infinity, }) const data = React.use(query.promise) return <>{data}</> } function Loading() { return <>loading..</> } function Page() { const enabledState = React.useState(false) const enabled = enabledState[0] const setEnabled = enabledState[1] return ( <div> <button onClick={() => setEnabled(true)}>enable</button> <React.Suspense fallback={<Loading />}> <MyComponent enabled={enabled} /> </React.Suspense> </div> ) } const rendered = await renderStream.render( <QueryClientProvider client={queryClient}> <Page /> </QueryClientProvider>, ) { const result = await renderStream.takeRender() result.withinDOM().getByText('loading..') } expect(queryFn).toHaveBeenCalledTimes(0) rendered.getByText('enable').click() { const result = await renderStream.takeRender() result.withinDOM().getByText('loading..') } expect(queryFn).toHaveBeenCalledTimes(1) { const result = await renderStream.takeRender() result.withinDOM().getByText('test') } expect(queryFn).toHaveBeenCalledTimes(1) }) })