|
1 | 1 | import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' |
| 2 | +import { getCurrentInstance } from 'vue-demi' |
2 | 3 | import { sleep } from '@tanstack/query-test-utils' |
3 | 4 | import { useInfiniteQuery } from '../useInfiniteQuery' |
4 | 5 | import { infiniteQueryOptions } from '../infiniteQueryOptions' |
| 6 | +import type { Mock } from 'vitest' |
5 | 7 |
|
6 | 8 | vi.mock('../useQueryClient') |
| 9 | +vi.mock('../useBaseQuery') |
7 | 10 |
|
8 | 11 | describe('useInfiniteQuery', () => { |
9 | 12 | beforeEach(() => { |
@@ -76,4 +79,73 @@ describe('useInfiniteQuery', () => { |
76 | 79 | }) |
77 | 80 | expect(status.value).toStrictEqual('success') |
78 | 81 | }) |
| 82 | + |
| 83 | + describe('throwOnError', () => { |
| 84 | + test('should throw from error watcher when throwOnError is true and suspense is not used', async () => { |
| 85 | + const throwOnErrorFn = vi.fn().mockReturnValue(true) |
| 86 | + useInfiniteQuery({ |
| 87 | + queryKey: ['infiniteThrowOnErrorWithoutSuspense'], |
| 88 | + queryFn: () => |
| 89 | + sleep(10).then(() => Promise.reject(new Error('Some error'))), |
| 90 | + initialPageParam: 0, |
| 91 | + getNextPageParam: () => 12, |
| 92 | + retry: false, |
| 93 | + throwOnError: throwOnErrorFn, |
| 94 | + }) |
| 95 | + |
| 96 | + // Suppress the Unhandled Rejection caused by watcher throw in Vue 3 |
| 97 | + const rejectionHandler = () => {} |
| 98 | + process.on('unhandledRejection', rejectionHandler) |
| 99 | + |
| 100 | + await vi.advanceTimersByTimeAsync(10) |
| 101 | + |
| 102 | + process.off('unhandledRejection', rejectionHandler) |
| 103 | + |
| 104 | + // throwOnError is evaluated and throw is attempted (not suppressed by suspense) |
| 105 | + expect(throwOnErrorFn).toHaveBeenCalledTimes(1) |
| 106 | + expect(throwOnErrorFn).toHaveBeenCalledWith( |
| 107 | + Error('Some error'), |
| 108 | + expect.objectContaining({ |
| 109 | + state: expect.objectContaining({ status: 'error' }), |
| 110 | + }), |
| 111 | + ) |
| 112 | + }) |
| 113 | + }) |
| 114 | + |
| 115 | + describe('suspense', () => { |
| 116 | + test('should not throw from error watcher when suspense is handling the error with throwOnError: true', async () => { |
| 117 | + const getCurrentInstanceSpy = getCurrentInstance as Mock |
| 118 | + getCurrentInstanceSpy.mockImplementation(() => ({ suspense: {} })) |
| 119 | + |
| 120 | + const throwOnErrorFn = vi.fn().mockReturnValue(true) |
| 121 | + const query = useInfiniteQuery({ |
| 122 | + queryKey: ['infiniteSuspenseThrowOnError'], |
| 123 | + queryFn: () => |
| 124 | + sleep(10).then(() => Promise.reject(new Error('Some error'))), |
| 125 | + initialPageParam: 0, |
| 126 | + getNextPageParam: () => 12, |
| 127 | + retry: false, |
| 128 | + throwOnError: throwOnErrorFn, |
| 129 | + }) |
| 130 | + |
| 131 | + let rejectedError: unknown |
| 132 | + const promise = query.suspense().catch((error) => { |
| 133 | + rejectedError = error |
| 134 | + }) |
| 135 | + |
| 136 | + await vi.advanceTimersByTimeAsync(10) |
| 137 | + |
| 138 | + await promise |
| 139 | + |
| 140 | + expect(rejectedError).toBeInstanceOf(Error) |
| 141 | + expect((rejectedError as Error).message).toBe('Some error') |
| 142 | + // throwOnError is evaluated in both suspense() and the error watcher |
| 143 | + expect(throwOnErrorFn).toHaveBeenCalledTimes(2) |
| 144 | + // but the error watcher should not throw when suspense is active |
| 145 | + expect(query).toMatchObject({ |
| 146 | + status: { value: 'error' }, |
| 147 | + isError: { value: true }, |
| 148 | + }) |
| 149 | + }) |
| 150 | + }) |
79 | 151 | }) |
0 commit comments