@tanstack/react-query 5.56.1 → 5.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,13 @@
1
- import { describe, expect, expectTypeOf, it, test, vi } from 'vitest'
1
+ import {
2
+ afterAll,
3
+ beforeAll,
4
+ describe,
5
+ expect,
6
+ expectTypeOf,
7
+ it,
8
+ test,
9
+ vi,
10
+ } from 'vitest'
2
11
  import { act, fireEvent, render, waitFor } from '@testing-library/react'
3
12
  import * as React from 'react'
4
13
  import { ErrorBoundary } from 'react-error-boundary'
@@ -25,7 +34,9 @@ import type { Mock } from 'vitest'
25
34
 
26
35
  describe('useQuery', () => {
27
36
  const queryCache = new QueryCache()
28
- const queryClient = createQueryClient({ queryCache })
37
+ const queryClient = createQueryClient({
38
+ queryCache,
39
+ })
29
40
 
30
41
  it('should return the correct types', () => {
31
42
  const key = queryKey()
@@ -41,6 +52,7 @@ describe('useQuery', () => {
41
52
  const fromQueryFn = useQuery({ queryKey: key, queryFn: () => 'test' })
42
53
  expectTypeOf(fromQueryFn.data).toEqualTypeOf<string | undefined>()
43
54
  expectTypeOf(fromQueryFn.error).toEqualTypeOf<Error | null>()
55
+ expectTypeOf(fromQueryFn.promise).toEqualTypeOf<Promise<string>>()
44
56
 
45
57
  // it should be possible to specify the result type
46
58
  const withResult = useQuery<string>({
@@ -270,6 +282,7 @@ describe('useQuery', () => {
270
282
  refetch: expect.any(Function),
271
283
  status: 'pending',
272
284
  fetchStatus: 'fetching',
285
+ promise: expect.any(Promise),
273
286
  })
274
287
 
275
288
  expect(states[1]).toEqual({
@@ -297,18 +310,22 @@ describe('useQuery', () => {
297
310
  refetch: expect.any(Function),
298
311
  status: 'success',
299
312
  fetchStatus: 'idle',
313
+ promise: expect.any(Promise),
300
314
  })
315
+
316
+ expect(states[0]!.promise).toEqual(states[1]!.promise)
301
317
  })
302
318
 
303
319
  it('should return the correct states for an unsuccessful query', async () => {
304
320
  const key = queryKey()
305
321
 
306
322
  const states: Array<UseQueryResult> = []
323
+ let index = 0
307
324
 
308
325
  function Page() {
309
326
  const state = useQuery({
310
327
  queryKey: key,
311
- queryFn: () => Promise.reject(new Error('rejected')),
328
+ queryFn: () => Promise.reject(new Error(`rejected #${++index}`)),
312
329
 
313
330
  retry: 1,
314
331
  retryDelay: 1,
@@ -354,6 +371,7 @@ describe('useQuery', () => {
354
371
  refetch: expect.any(Function),
355
372
  status: 'pending',
356
373
  fetchStatus: 'fetching',
374
+ promise: expect.any(Promise),
357
375
  })
358
376
 
359
377
  expect(states[1]).toEqual({
@@ -362,7 +380,7 @@ describe('useQuery', () => {
362
380
  error: null,
363
381
  errorUpdatedAt: 0,
364
382
  failureCount: 1,
365
- failureReason: new Error('rejected'),
383
+ failureReason: new Error('rejected #1'),
366
384
  errorUpdateCount: 0,
367
385
  isError: false,
368
386
  isFetched: false,
@@ -381,15 +399,16 @@ describe('useQuery', () => {
381
399
  refetch: expect.any(Function),
382
400
  status: 'pending',
383
401
  fetchStatus: 'fetching',
402
+ promise: expect.any(Promise),
384
403
  })
385
404
 
386
405
  expect(states[2]).toEqual({
387
406
  data: undefined,
388
407
  dataUpdatedAt: 0,
389
- error: new Error('rejected'),
408
+ error: new Error('rejected #2'),
390
409
  errorUpdatedAt: expect.any(Number),
391
410
  failureCount: 2,
392
- failureReason: new Error('rejected'),
411
+ failureReason: new Error('rejected #2'),
393
412
  errorUpdateCount: 1,
394
413
  isError: true,
395
414
  isFetched: true,
@@ -408,7 +427,11 @@ describe('useQuery', () => {
408
427
  refetch: expect.any(Function),
409
428
  status: 'error',
410
429
  fetchStatus: 'idle',
430
+ promise: expect.any(Promise),
411
431
  })
432
+
433
+ expect(states[0]!.promise).toEqual(states[1]!.promise)
434
+ expect(states[1]!.promise).toEqual(states[2]!.promise)
412
435
  })
413
436
 
414
437
  it('should set isFetchedAfterMount to true after a query has been fetched', async () => {
@@ -659,7 +682,7 @@ describe('useQuery', () => {
659
682
  },
660
683
 
661
684
  gcTime: 0,
662
- notifyOnChangeProps: 'all',
685
+ notifyOnChangeProps: ['isPending', 'isSuccess', 'data'],
663
686
  })
664
687
 
665
688
  states.push(state)
@@ -697,13 +720,29 @@ describe('useQuery', () => {
697
720
 
698
721
  expect(states.length).toBe(4)
699
722
  // First load
700
- expect(states[0]).toMatchObject({ isPending: true, isSuccess: false })
723
+ expect(states[0]).toMatchObject({
724
+ isPending: true,
725
+ isSuccess: false,
726
+ data: undefined,
727
+ })
701
728
  // First success
702
- expect(states[1]).toMatchObject({ isPending: false, isSuccess: true })
729
+ expect(states[1]).toMatchObject({
730
+ isPending: false,
731
+ isSuccess: true,
732
+ data: 'data',
733
+ })
703
734
  // Remove
704
- expect(states[2]).toMatchObject({ isPending: true, isSuccess: false })
735
+ expect(states[2]).toMatchObject({
736
+ isPending: true,
737
+ isSuccess: false,
738
+ data: undefined,
739
+ })
705
740
  // Second success
706
- expect(states[3]).toMatchObject({ isPending: false, isSuccess: true })
741
+ expect(states[3]).toMatchObject({
742
+ isPending: false,
743
+ isSuccess: true,
744
+ data: 'data',
745
+ })
707
746
  })
708
747
 
709
748
  it('should fetch when refetchOnMount is false and nothing has been fetched yet', async () => {
@@ -6581,4 +6620,747 @@ describe('useQuery', () => {
6581
6620
 
6582
6621
  consoleMock.mockRestore()
6583
6622
  })
6623
+
6624
+ describe('useQuery().promise', () => {
6625
+ beforeAll(() => {
6626
+ queryClient.setDefaultOptions({
6627
+ queries: { experimental_prefetchInRender: true },
6628
+ })
6629
+ })
6630
+ afterAll(() => {
6631
+ queryClient.setDefaultOptions({
6632
+ queries: { experimental_prefetchInRender: false },
6633
+ })
6634
+ })
6635
+ it('should work with a basic test', async () => {
6636
+ const key = queryKey()
6637
+ let suspenseRenderCount = 0
6638
+ let pageRenderCount = 0
6639
+
6640
+ function MyComponent(props: { promise: Promise<string> }) {
6641
+ const data = React.use(props.promise)
6642
+
6643
+ return <>{data}</>
6644
+ }
6645
+
6646
+ function Loading() {
6647
+ suspenseRenderCount++
6648
+ return <>loading..</>
6649
+ }
6650
+ function Page() {
6651
+ const query = useQuery({
6652
+ queryKey: key,
6653
+ queryFn: async () => {
6654
+ await sleep(1)
6655
+ return 'test'
6656
+ },
6657
+ })
6658
+
6659
+ pageRenderCount++
6660
+ return (
6661
+ <React.Suspense fallback={<Loading />}>
6662
+ <MyComponent promise={query.promise} />
6663
+ </React.Suspense>
6664
+ )
6665
+ }
6666
+
6667
+ const rendered = renderWithClient(queryClient, <Page />)
6668
+ await waitFor(() => rendered.getByText('loading..'))
6669
+ await waitFor(() => rendered.getByText('test'))
6670
+
6671
+ // Suspense should rendered once since `.promise` is the only watched property
6672
+ expect(suspenseRenderCount).toBe(1)
6673
+
6674
+ // Page should be rendered once since since the promise do not change
6675
+ expect(pageRenderCount).toBe(1)
6676
+ })
6677
+
6678
+ it('colocate suspense and promise', async () => {
6679
+ const key = queryKey()
6680
+ let suspenseRenderCount = 0
6681
+ let pageRenderCount = 0
6682
+ let callCount = 0
6683
+
6684
+ function MyComponent() {
6685
+ const query = useQuery({
6686
+ queryKey: key,
6687
+ queryFn: async () => {
6688
+ callCount++
6689
+ await sleep(1)
6690
+ return 'test'
6691
+ },
6692
+ staleTime: 1000,
6693
+ })
6694
+ const data = React.use(query.promise)
6695
+
6696
+ return <>{data}</>
6697
+ }
6698
+
6699
+ function Loading() {
6700
+ suspenseRenderCount++
6701
+ return <>loading..</>
6702
+ }
6703
+ function Page() {
6704
+ pageRenderCount++
6705
+ return (
6706
+ <React.Suspense fallback={<Loading />}>
6707
+ <MyComponent />
6708
+ </React.Suspense>
6709
+ )
6710
+ }
6711
+
6712
+ const rendered = renderWithClient(queryClient, <Page />)
6713
+ await waitFor(() => rendered.getByText('loading..'))
6714
+ await waitFor(() => rendered.getByText('test'))
6715
+
6716
+ // Suspense should rendered once since `.promise` is the only watched property
6717
+ expect(suspenseRenderCount).toBe(1)
6718
+
6719
+ // Page should be rendered once since since the promise do not change
6720
+ expect(pageRenderCount).toBe(1)
6721
+
6722
+ expect(callCount).toBe(1)
6723
+ })
6724
+
6725
+ it('parallel queries', async () => {
6726
+ const key = queryKey()
6727
+ let suspenseRenderCount = 0
6728
+ let pageRenderCount = 0
6729
+ let callCount = 0
6730
+
6731
+ function MyComponent() {
6732
+ const query = useQuery({
6733
+ queryKey: key,
6734
+ queryFn: async () => {
6735
+ callCount++
6736
+ await sleep(1)
6737
+ return 'test'
6738
+ },
6739
+ staleTime: 1000,
6740
+ })
6741
+ const data = React.use(query.promise)
6742
+
6743
+ return data
6744
+ }
6745
+
6746
+ function Loading() {
6747
+ suspenseRenderCount++
6748
+ return <>loading..</>
6749
+ }
6750
+ function Page() {
6751
+ pageRenderCount++
6752
+ return (
6753
+ <>
6754
+ <React.Suspense fallback={<Loading />}>
6755
+ <MyComponent />
6756
+ <MyComponent />
6757
+ <MyComponent />
6758
+ </React.Suspense>
6759
+ <React.Suspense fallback={null}>
6760
+ <MyComponent />
6761
+ <MyComponent />
6762
+ </React.Suspense>
6763
+ </>
6764
+ )
6765
+ }
6766
+
6767
+ const rendered = renderWithClient(queryClient, <Page />)
6768
+ await waitFor(() => rendered.getByText('loading..'))
6769
+ await waitFor(() => {
6770
+ expect(rendered.queryByText('loading..')).not.toBeInTheDocument()
6771
+ })
6772
+
6773
+ expect(rendered.container.textContent).toBe('test'.repeat(5))
6774
+
6775
+ // Suspense should rendered once since `.promise` is the only watched property
6776
+ expect(suspenseRenderCount).toBe(1)
6777
+
6778
+ // Page should be rendered once since since the promise do not change
6779
+ expect(pageRenderCount).toBe(1)
6780
+
6781
+ expect(callCount).toBe(1)
6782
+ })
6783
+
6784
+ it('should work with initial data', async () => {
6785
+ const key = queryKey()
6786
+ let suspenseRenderCount = 0
6787
+ let pageRenderCount = 0
6788
+
6789
+ function MyComponent(props: { promise: Promise<string> }) {
6790
+ const data = React.use(props.promise)
6791
+
6792
+ return <>{data}</>
6793
+ }
6794
+ function Loading() {
6795
+ suspenseRenderCount++
6796
+
6797
+ return <>loading..</>
6798
+ }
6799
+ function Page() {
6800
+ const query = useQuery({
6801
+ queryKey: key,
6802
+ queryFn: async () => {
6803
+ await sleep(1)
6804
+ return 'test'
6805
+ },
6806
+ initialData: 'initial',
6807
+ })
6808
+ pageRenderCount++
6809
+
6810
+ return (
6811
+ <React.Suspense fallback={<Loading />}>
6812
+ <MyComponent promise={query.promise} />
6813
+ </React.Suspense>
6814
+ )
6815
+ }
6816
+
6817
+ const rendered = renderWithClient(queryClient, <Page />)
6818
+ await waitFor(() => rendered.getByText('initial'))
6819
+ await waitFor(() => rendered.getByText('test'))
6820
+
6821
+ // Suspense boundary should never be rendered since it has data immediately
6822
+ expect(suspenseRenderCount).toBe(0)
6823
+ // Page should only be rendered twice since, the promise will get swapped out when new result comes in
6824
+ expect(pageRenderCount).toBe(2)
6825
+ })
6826
+
6827
+ it('should not fetch with initial data and staleTime', async () => {
6828
+ const key = queryKey()
6829
+ let suspenseRenderCount = 0
6830
+ const queryFn = vi.fn().mockImplementation(async () => {
6831
+ await sleep(1)
6832
+ return 'test'
6833
+ })
6834
+
6835
+ function MyComponent(props: { promise: Promise<string> }) {
6836
+ const data = React.use(props.promise)
6837
+
6838
+ return <>{data}</>
6839
+ }
6840
+ function Loading() {
6841
+ suspenseRenderCount++
6842
+
6843
+ return <>loading..</>
6844
+ }
6845
+ function Page() {
6846
+ const query = useQuery({
6847
+ queryKey: key,
6848
+ queryFn,
6849
+ initialData: 'initial',
6850
+ staleTime: 1000,
6851
+ })
6852
+
6853
+ return (
6854
+ <React.Suspense fallback={<Loading />}>
6855
+ <MyComponent promise={query.promise} />
6856
+ </React.Suspense>
6857
+ )
6858
+ }
6859
+
6860
+ const rendered = renderWithClient(queryClient, <Page />)
6861
+ await waitFor(() => rendered.getByText('initial'))
6862
+
6863
+ // Suspense boundary should never be rendered since it has data immediately
6864
+ expect(suspenseRenderCount).toBe(0)
6865
+ // should not call queryFn because of staleTime + initialData combo
6866
+ expect(queryFn).toHaveBeenCalledTimes(0)
6867
+ })
6868
+
6869
+ it('should work with static placeholderData', async () => {
6870
+ const key = queryKey()
6871
+ let suspenseRenderCount = 0
6872
+ let pageRenderCount = 0
6873
+
6874
+ function MyComponent(props: { promise: Promise<string> }) {
6875
+ const data = React.use(props.promise)
6876
+
6877
+ return <>{data}</>
6878
+ }
6879
+ function Loading() {
6880
+ suspenseRenderCount++
6881
+
6882
+ return <>loading..</>
6883
+ }
6884
+ function Page() {
6885
+ const query = useQuery({
6886
+ queryKey: key,
6887
+ queryFn: async () => {
6888
+ await sleep(1)
6889
+ return 'test'
6890
+ },
6891
+ placeholderData: 'placeholder',
6892
+ })
6893
+ pageRenderCount++
6894
+
6895
+ return (
6896
+ <React.Suspense fallback={<Loading />}>
6897
+ <MyComponent promise={query.promise} />
6898
+ </React.Suspense>
6899
+ )
6900
+ }
6901
+
6902
+ const rendered = renderWithClient(queryClient, <Page />)
6903
+ await waitFor(() => rendered.getByText('placeholder'))
6904
+ await waitFor(() => rendered.getByText('test'))
6905
+
6906
+ // Suspense boundary should never be rendered since it has data immediately
6907
+ expect(suspenseRenderCount).toBe(0)
6908
+ // Page should only be rendered twice since, the promise will get swapped out when new result comes in
6909
+ expect(pageRenderCount).toBe(2)
6910
+ })
6911
+
6912
+ it('should work with placeholderData: keepPreviousData', async () => {
6913
+ const key = queryKey()
6914
+ let suspenseRenderCount = 0
6915
+
6916
+ function MyComponent(props: { promise: Promise<string> }) {
6917
+ const data = React.use(props.promise)
6918
+
6919
+ return <>{data}</>
6920
+ }
6921
+ function Loading() {
6922
+ suspenseRenderCount++
6923
+
6924
+ return <>loading..</>
6925
+ }
6926
+ function Page() {
6927
+ const [count, setCount] = React.useState(0)
6928
+ const query = useQuery({
6929
+ queryKey: [...key, count],
6930
+ queryFn: async () => {
6931
+ await sleep(1)
6932
+ return 'test-' + count
6933
+ },
6934
+ placeholderData: keepPreviousData,
6935
+ })
6936
+
6937
+ return (
6938
+ <div>
6939
+ <React.Suspense fallback={<Loading />}>
6940
+ <MyComponent promise={query.promise} />
6941
+ </React.Suspense>
6942
+ <button onClick={() => setCount((c) => c + 1)}>increment</button>
6943
+ </div>
6944
+ )
6945
+ }
6946
+
6947
+ const rendered = renderWithClient(queryClient, <Page />)
6948
+ await waitFor(() => rendered.getByText('loading..'))
6949
+ await waitFor(() => rendered.getByText('test-0'))
6950
+
6951
+ // Suspense boundary should only be rendered initially
6952
+ expect(suspenseRenderCount).toBe(1)
6953
+
6954
+ fireEvent.click(rendered.getByRole('button', { name: 'increment' }))
6955
+
6956
+ await waitFor(() => rendered.getByText('test-1'))
6957
+
6958
+ // no more suspense boundary rendering
6959
+ expect(suspenseRenderCount).toBe(1)
6960
+ })
6961
+
6962
+ it('should be possible to select a part of the data with select', async () => {
6963
+ const key = queryKey()
6964
+ let suspenseRenderCount = 0
6965
+ let pageRenderCount = 0
6966
+
6967
+ function MyComponent(props: { promise: Promise<string> }) {
6968
+ const data = React.use(props.promise)
6969
+ return <>{data}</>
6970
+ }
6971
+
6972
+ function Loading() {
6973
+ suspenseRenderCount++
6974
+ return <>loading..</>
6975
+ }
6976
+
6977
+ function Page() {
6978
+ const query = useQuery({
6979
+ queryKey: key,
6980
+ queryFn: async () => {
6981
+ await sleep(1)
6982
+ return { name: 'test' }
6983
+ },
6984
+ select: (data) => data.name,
6985
+ })
6986
+
6987
+ pageRenderCount++
6988
+ return (
6989
+ <React.Suspense fallback={<Loading />}>
6990
+ <MyComponent promise={query.promise} />
6991
+ </React.Suspense>
6992
+ )
6993
+ }
6994
+
6995
+ const rendered = renderWithClient(queryClient, <Page />)
6996
+
6997
+ await waitFor(() => {
6998
+ rendered.getByText('test')
6999
+ })
7000
+ expect(suspenseRenderCount).toBe(1)
7001
+ expect(pageRenderCount).toBe(1)
7002
+ })
7003
+
7004
+ it('should throw error if the promise fails', async () => {
7005
+ let suspenseRenderCount = 0
7006
+ const consoleMock = vi
7007
+ .spyOn(console, 'error')
7008
+ .mockImplementation(() => undefined)
7009
+
7010
+ const key = queryKey()
7011
+ function MyComponent(props: { promise: Promise<string> }) {
7012
+ const data = React.use(props.promise)
7013
+
7014
+ return <>{data}</>
7015
+ }
7016
+
7017
+ function Loading() {
7018
+ suspenseRenderCount++
7019
+ return <>loading..</>
7020
+ }
7021
+
7022
+ let queryCount = 0
7023
+ function Page() {
7024
+ const query = useQuery({
7025
+ queryKey: key,
7026
+ queryFn: async () => {
7027
+ await sleep(1)
7028
+ if (++queryCount > 1) {
7029
+ // second time this query mounts, it should not throw
7030
+ return 'data'
7031
+ }
7032
+ throw new Error('Error test')
7033
+ },
7034
+ retry: false,
7035
+ })
7036
+
7037
+ return (
7038
+ <React.Suspense fallback={<Loading />}>
7039
+ <MyComponent promise={query.promise} />
7040
+ </React.Suspense>
7041
+ )
7042
+ }
7043
+
7044
+ const rendered = renderWithClient(
7045
+ queryClient,
7046
+ <ErrorBoundary
7047
+ fallbackRender={(props) => (
7048
+ <>
7049
+ error boundary{' '}
7050
+ <button
7051
+ onClick={() => {
7052
+ props.resetErrorBoundary()
7053
+ }}
7054
+ >
7055
+ resetErrorBoundary
7056
+ </button>
7057
+ </>
7058
+ )}
7059
+ >
7060
+ <Page />
7061
+ </ErrorBoundary>,
7062
+ )
7063
+
7064
+ await waitFor(() => rendered.getByText('loading..'))
7065
+ await waitFor(() => rendered.getByText('error boundary'))
7066
+
7067
+ consoleMock.mockRestore()
7068
+
7069
+ fireEvent.click(rendered.getByText('resetErrorBoundary'))
7070
+
7071
+ await waitFor(() => rendered.getByText('loading..'))
7072
+ await waitFor(() => rendered.getByText('data'))
7073
+
7074
+ expect(queryCount).toBe(2)
7075
+ })
7076
+
7077
+ it('should recreate promise with data changes', async () => {
7078
+ const key = queryKey()
7079
+ let suspenseRenderCount = 0
7080
+ let pageRenderCount = 0
7081
+
7082
+ function MyComponent(props: { promise: Promise<string> }) {
7083
+ const data = React.use(props.promise)
7084
+
7085
+ return <>{data}</>
7086
+ }
7087
+
7088
+ function Loading() {
7089
+ suspenseRenderCount++
7090
+ return <>loading..</>
7091
+ }
7092
+ function Page() {
7093
+ const query = useQuery({
7094
+ queryKey: key,
7095
+ queryFn: async () => {
7096
+ await sleep(1)
7097
+ return 'test1'
7098
+ },
7099
+ })
7100
+
7101
+ pageRenderCount++
7102
+ return (
7103
+ <React.Suspense fallback={<Loading />}>
7104
+ <MyComponent promise={query.promise} />
7105
+ </React.Suspense>
7106
+ )
7107
+ }
7108
+
7109
+ const rendered = renderWithClient(queryClient, <Page />)
7110
+ await waitFor(() => rendered.getByText('loading..'))
7111
+ await waitFor(() => rendered.getByText('test1'))
7112
+
7113
+ // Suspense should rendered once since `.promise` is the only watched property
7114
+ expect(pageRenderCount).toBe(1)
7115
+
7116
+ queryClient.setQueryData(key, 'test2')
7117
+
7118
+ await waitFor(() => rendered.getByText('test2'))
7119
+
7120
+ // Suspense should rendered once since `.promise` is the only watched property
7121
+ expect(suspenseRenderCount).toBe(1)
7122
+
7123
+ // Page should be rendered once since since the promise changed once
7124
+ expect(pageRenderCount).toBe(2)
7125
+ })
7126
+
7127
+ it('should dedupe when re-fetched with queryClient.fetchQuery while suspending', async () => {
7128
+ const key = queryKey()
7129
+ const queryFn = vi.fn().mockImplementation(async () => {
7130
+ await sleep(10)
7131
+ return 'test'
7132
+ })
7133
+
7134
+ const options = {
7135
+ queryKey: key,
7136
+ queryFn,
7137
+ }
7138
+
7139
+ function MyComponent(props: { promise: Promise<string> }) {
7140
+ const data = React.use(props.promise)
7141
+
7142
+ return <>{data}</>
7143
+ }
7144
+
7145
+ function Loading() {
7146
+ return <>loading..</>
7147
+ }
7148
+ function Page() {
7149
+ const query = useQuery(options)
7150
+
7151
+ return (
7152
+ <div>
7153
+ <React.Suspense fallback={<Loading />}>
7154
+ <MyComponent promise={query.promise} />
7155
+ </React.Suspense>
7156
+ <button onClick={() => queryClient.fetchQuery(options)}>
7157
+ fetch
7158
+ </button>
7159
+ </div>
7160
+ )
7161
+ }
7162
+
7163
+ const rendered = renderWithClient(queryClient, <Page />)
7164
+ fireEvent.click(rendered.getByText('fetch'))
7165
+ await waitFor(() => rendered.getByText('loading..'))
7166
+ await waitFor(() => rendered.getByText('test'))
7167
+
7168
+ expect(queryFn).toHaveBeenCalledOnce()
7169
+ })
7170
+
7171
+ it('should dedupe when re-fetched with refetchQueries while suspending', async () => {
7172
+ const key = queryKey()
7173
+ let count = 0
7174
+ const queryFn = vi.fn().mockImplementation(async () => {
7175
+ await sleep(10)
7176
+ return 'test' + count++
7177
+ })
7178
+
7179
+ const options = {
7180
+ queryKey: key,
7181
+ queryFn,
7182
+ }
7183
+
7184
+ function MyComponent(props: { promise: Promise<string> }) {
7185
+ const data = React.use(props.promise)
7186
+
7187
+ return <>{data}</>
7188
+ }
7189
+
7190
+ function Loading() {
7191
+ return <>loading..</>
7192
+ }
7193
+ function Page() {
7194
+ const query = useQuery(options)
7195
+
7196
+ return (
7197
+ <div>
7198
+ <React.Suspense fallback={<Loading />}>
7199
+ <MyComponent promise={query.promise} />
7200
+ </React.Suspense>
7201
+ <button onClick={() => queryClient.refetchQueries(options)}>
7202
+ refetch
7203
+ </button>
7204
+ </div>
7205
+ )
7206
+ }
7207
+
7208
+ const rendered = renderWithClient(queryClient, <Page />)
7209
+ fireEvent.click(rendered.getByText('refetch'))
7210
+ await waitFor(() => rendered.getByText('loading..'))
7211
+ await waitFor(() => rendered.getByText('test0'))
7212
+
7213
+ expect(queryFn).toHaveBeenCalledOnce()
7214
+ })
7215
+
7216
+ it('should stay pending when canceled with cancelQueries while suspending until refetched', async () => {
7217
+ const key = queryKey()
7218
+ let count = 0
7219
+ const queryFn = vi.fn().mockImplementation(async () => {
7220
+ await sleep(10)
7221
+ return 'test' + count++
7222
+ })
7223
+
7224
+ const options = {
7225
+ queryKey: key,
7226
+ queryFn,
7227
+ }
7228
+
7229
+ function MyComponent(props: { promise: Promise<string> }) {
7230
+ const data = React.use(props.promise)
7231
+
7232
+ return <>{data}</>
7233
+ }
7234
+
7235
+ function Loading() {
7236
+ return <>loading..</>
7237
+ }
7238
+ function Page() {
7239
+ const query = useQuery(options)
7240
+
7241
+ return (
7242
+ <div>
7243
+ <React.Suspense fallback={<Loading />}>
7244
+ <MyComponent promise={query.promise} />
7245
+ </React.Suspense>
7246
+ <button onClick={() => queryClient.cancelQueries(options)}>
7247
+ cancel
7248
+ </button>
7249
+ <button
7250
+ onClick={() => queryClient.setQueryData<string>(key, 'hello')}
7251
+ >
7252
+ fetch
7253
+ </button>
7254
+ </div>
7255
+ )
7256
+ }
7257
+
7258
+ const rendered = renderWithClient(
7259
+ queryClient,
7260
+ <ErrorBoundary fallbackRender={() => <>error boundary</>}>
7261
+ <Page />
7262
+ </ErrorBoundary>,
7263
+ )
7264
+ fireEvent.click(rendered.getByText('cancel'))
7265
+ await waitFor(() => rendered.getByText('loading..'))
7266
+ // await waitFor(() => rendered.getByText('error boundary'))
7267
+ await waitFor(() =>
7268
+ expect(queryClient.getQueryState(key)).toMatchObject({
7269
+ status: 'pending',
7270
+ fetchStatus: 'idle',
7271
+ }),
7272
+ )
7273
+
7274
+ expect(queryFn).toHaveBeenCalledOnce()
7275
+
7276
+ fireEvent.click(rendered.getByText('fetch'))
7277
+
7278
+ await waitFor(() => rendered.getByText('hello'))
7279
+ })
7280
+
7281
+ it('should resolve to previous data when canceled with cancelQueries while suspending', async () => {
7282
+ const key = queryKey()
7283
+ const queryFn = vi.fn().mockImplementation(async () => {
7284
+ await sleep(10)
7285
+ return 'test'
7286
+ })
7287
+
7288
+ const options = {
7289
+ queryKey: key,
7290
+ queryFn,
7291
+ }
7292
+
7293
+ function MyComponent(props: { promise: Promise<string> }) {
7294
+ const data = React.use(props.promise)
7295
+
7296
+ return <>{data}</>
7297
+ }
7298
+
7299
+ function Loading() {
7300
+ return <>loading..</>
7301
+ }
7302
+ function Page() {
7303
+ const query = useQuery(options)
7304
+
7305
+ return (
7306
+ <div>
7307
+ <React.Suspense fallback={<Loading />}>
7308
+ <MyComponent promise={query.promise} />
7309
+ </React.Suspense>
7310
+ <button onClick={() => queryClient.cancelQueries(options)}>
7311
+ cancel
7312
+ </button>
7313
+ </div>
7314
+ )
7315
+ }
7316
+
7317
+ queryClient.setQueryData(key, 'initial')
7318
+
7319
+ const rendered = renderWithClient(queryClient, <Page />)
7320
+ fireEvent.click(rendered.getByText('cancel'))
7321
+ await waitFor(() => rendered.getByText('initial'))
7322
+
7323
+ expect(queryFn).toHaveBeenCalledTimes(1)
7324
+ })
7325
+
7326
+ it('should suspend when not enabled', async () => {
7327
+ const key = queryKey()
7328
+
7329
+ const options = (count: number) => ({
7330
+ queryKey: [...key, count],
7331
+ queryFn: async () => {
7332
+ await sleep(10)
7333
+ return 'test' + count
7334
+ },
7335
+ })
7336
+
7337
+ function MyComponent(props: { promise: Promise<string> }) {
7338
+ const data = React.use(props.promise)
7339
+
7340
+ return <>{data}</>
7341
+ }
7342
+
7343
+ function Loading() {
7344
+ return <>loading..</>
7345
+ }
7346
+ function Page() {
7347
+ const [count, setCount] = React.useState(0)
7348
+ const query = useQuery({ ...options(count), enabled: count > 0 })
7349
+
7350
+ return (
7351
+ <div>
7352
+ <React.Suspense fallback={<Loading />}>
7353
+ <MyComponent promise={query.promise} />
7354
+ </React.Suspense>
7355
+ <button onClick={() => setCount(1)}>enable</button>
7356
+ </div>
7357
+ )
7358
+ }
7359
+
7360
+ const rendered = renderWithClient(queryClient, <Page />)
7361
+ await waitFor(() => rendered.getByText('loading..'))
7362
+ fireEvent.click(rendered.getByText('enable'))
7363
+ await waitFor(() => rendered.getByText('test1'))
7364
+ })
7365
+ })
6584
7366
  })