From d900f122a9447c57a4132f7051d1e2981441789b Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 21:29:59 +0900 Subject: [PATCH 01/43] test(query-core/queryObserver): add type tests for its public surface --- .../src/__tests__/queryObserver.test-d.tsx | 878 +++++++++++++++++- 1 file changed, 873 insertions(+), 5 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 90db06e902..6440f39d87 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1,7 +1,24 @@ import { afterEach, beforeEach, describe, expectTypeOf, it } from 'vitest' import { queryKey } from '@tanstack/query-test-utils' import { QueryClient, QueryObserver } from '..' -import type { DefaultError, QueryObserverResult } from '..' +import type { + DefaultError, + FetchStatus, + InitialDataFunction, + NetworkMode, + NotifyOnChangeProps, + PlaceholderDataFunction, + QueryMeta, + QueryObserverOptions, + QueryObserverResult, + QueryPersister, + QueryStatus, + RefetchOptions, +} from '..' + +class CustomError extends Error { + name = 'CustomError' as const +} describe('queryObserver', () => { let queryClient: QueryClient @@ -100,7 +117,856 @@ describe('queryObserver', () => { } }) + describe('the result', () => { + it('should type dataUpdatedAt as a number', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().dataUpdatedAt, + ).toEqualTypeOf() + }) + + it('should type errorUpdatedAt as a number', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().errorUpdatedAt, + ).toEqualTypeOf() + }) + + it('should type failureCount as a number', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().failureCount, + ).toEqualTypeOf() + }) + + it('should type errorUpdateCount as a number', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().errorUpdateCount, + ).toEqualTypeOf() + }) + + it('should type isFetching as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().isFetching, + ).toEqualTypeOf() + }) + + it('should type isRefetching as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().isRefetching, + ).toEqualTypeOf() + }) + + it('should type isPaused as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().isPaused, + ).toEqualTypeOf() + }) + + it('should type isStale as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getCurrentResult().isStale).toEqualTypeOf() + }) + + it('should type isEnabled as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().isEnabled, + ).toEqualTypeOf() + }) + + it('should type isFetched as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().isFetched, + ).toEqualTypeOf() + }) + + it('should type isFetchedAfterMount as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().isFetchedAfterMount, + ).toEqualTypeOf() + }) + + it('should type failureReason from the error type', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + }) + + expectTypeOf( + observer.getCurrentResult().failureReason, + ).toEqualTypeOf() + }) + + it('should type its refetch from the observed types', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getCurrentResult().refetch).returns.toEqualTypeOf< + Promise> + >() + }) + + it('should only accept RefetchOptions in its refetch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().refetch, + ).parameters.toEqualTypeOf<[options?: RefetchOptions]>() + }) + + it('should type status as the query status union', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().status, + ).toEqualTypeOf() + }) + + it('should type fetchStatus as the fetch status union', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().fetchStatus, + ).toEqualTypeOf() + }) + }) + + describe('narrowing the result', () => { + it('should narrow error to the error type on an isError check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isError) { + expectTypeOf(result.error).toEqualTypeOf() + } + }) + + it('should keep data possibly undefined on an isError check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isError) { + expectTypeOf(result.data).toEqualTypeOf<{ value: string } | undefined>() + } + }) + + it('should narrow data to be defined on a success status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'success') { + expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() + } + }) + + it('should narrow error to the error type on an error status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'error') { + expectTypeOf(result.error).toEqualTypeOf() + } + }) + + it('should narrow data to undefined on a pending status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'pending') { + expectTypeOf(result.data).toEqualTypeOf() + } + }) + }) + + describe('setOptions', () => { + it('should keep the observed data type in the options it is given', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + observer.setOptions({ + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + select: (data) => { + expectTypeOf(data).toEqualTypeOf<{ value: string }>() + return data + }, + }) + + observer.setOptions({ + queryKey: queryKey(), + // @ts-expect-error the queryFn must return the observed data type + queryFn: () => 42, + }) + }) + }) + + describe('the options', () => { + it('should type the queryKey given to the queryFn', () => { + const key = ['a', 1] as const + + new QueryObserver(queryClient, { + queryKey: key, + queryFn: (context) => { + expectTypeOf(context.queryKey).toEqualTypeOf() + return Promise.resolve('data') + }, + }) + }) + + it('should type the signal given to the queryFn', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: (context) => { + expectTypeOf(context.signal).toEqualTypeOf() + return Promise.resolve('data') + }, + }) + }) + + it('should type the client given to the queryFn', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: (context) => { + expectTypeOf(context.client).toEqualTypeOf() + return Promise.resolve('data') + }, + }) + }) + + it('should type the query given to an enabled callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + enabled: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) + }) + + it('should type the query given to a staleTime callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + staleTime: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return 0 + }, + }) + }) + + it('should type the query given to a refetchInterval callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchInterval: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return false + }, + }) + }) + + it('should type the query given to a refetchOnWindowFocus callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchOnWindowFocus: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) + }) + + it('should type the query given to a refetchOnReconnect callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchOnReconnect: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) + }) + + it('should type the query given to a refetchOnMount callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchOnMount: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) + }) + + it('should type the query given to a retryOnMount callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + retryOnMount: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) + }) + + it('should type persister from the queryFn data', () => { + expectTypeOf< + QueryObserverOptions<{ value: string }>['persister'] + >().toEqualTypeOf< + | QueryPersister<{ value: string }, ReadonlyArray, never> + | undefined + >() + }) + + it('should type meta as its named type', () => { + expectTypeOf().toEqualTypeOf< + QueryMeta | undefined + >() + }) + + it('should type notifyOnChangeProps as its named type', () => { + expectTypeOf().toEqualTypeOf< + NotifyOnChangeProps | undefined + >() + }) + + it('should type the error given to a throwOnError callback', () => { + new QueryObserver<{ value: string }, CustomError>(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + throwOnError: (error) => { + expectTypeOf(error).toEqualTypeOf() + return false + }, + }) + }) + + it('should type suspense as a boolean', () => { + expectTypeOf().toEqualTypeOf< + boolean | undefined + >() + }) + + it('should type refetchIntervalInBackground as a boolean', () => { + expectTypeOf< + QueryObserverOptions['refetchIntervalInBackground'] + >().toEqualTypeOf() + }) + + it('should reject a non-number gcTime', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error gcTime must be a number + gcTime: 'nope', + }) + + expectTypeOf().toEqualTypeOf< + number | undefined + >() + }) + + it('should type networkMode as the network mode union', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error networkMode must be one of the NetworkMode values + networkMode: 'nope', + }) + + expectTypeOf().toEqualTypeOf< + NetworkMode | undefined + >() + }) + + it('should reject a non-string queryHash', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error queryHash must be a string + queryHash: 42, + }) + + expectTypeOf().toEqualTypeOf< + string | undefined + >() + }) + + it('should accept a function for initialDataUpdatedAt', () => { + expectTypeOf< + QueryObserverOptions['initialDataUpdatedAt'] + >().toEqualTypeOf number | undefined) | undefined>() + }) + + it('should type the error given to a retry callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + retry: (_failureCount, error) => { + expectTypeOf(error).toEqualTypeOf() + return false + }, + }) + }) + + it('should type the error given to a retryDelay callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + retryDelay: (_failureCount, error) => { + expectTypeOf(error).toEqualTypeOf() + return 0 + }, + }) + }) + + it('should type the queryKey given to a queryKeyHashFn', () => { + const key = ['a', 1] as const + + new QueryObserver(queryClient, { + queryKey: key, + queryFn: () => Promise.resolve('data'), + queryKeyHashFn: (queryKeyToHash) => { + expectTypeOf(queryKeyToHash).toEqualTypeOf() + return 'hash' + }, + }) + }) + + it('should type an initialData function from the data type', () => { + expectTypeOf< + InitialDataFunction<{ value: string }> + >().returns.toEqualTypeOf<{ value: string } | undefined>() + }) + + it('should type a structuralSharing callback as unknown', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + structuralSharing: (_oldData, newData) => { + expectTypeOf(newData).toEqualTypeOf() + return newData + }, + }) + }) + }) + + describe('destroy', () => { + it('should return void', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.destroy).returns.toEqualTypeOf() + }) + }) + + describe('updateResult', () => { + it('should return void', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.updateResult).returns.toEqualTypeOf() + }) + }) + + describe('onQueryUpdate', () => { + it('should return void', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.onQueryUpdate).returns.toEqualTypeOf() + }) + }) + + describe('shouldFetchOnReconnect', () => { + it('should return a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.shouldFetchOnReconnect, + ).returns.toEqualTypeOf() + }) + }) + + describe('shouldFetchOnWindowFocus', () => { + it('should return a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.shouldFetchOnWindowFocus, + ).returns.toEqualTypeOf() + }) + }) + + describe('trackProp', () => { + it('should only accept a key of the result', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.trackProp).parameters.toEqualTypeOf< + [key: keyof QueryObserverResult] + >() + }) + }) + + describe('getOptimisticResult', () => { + it('should be typed from the options it is given', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const options = queryClient.defaultQueryOptions({ + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getOptimisticResult(options)).toEqualTypeOf< + QueryObserverResult<{ value: string }, DefaultError> + >() + }) + + it('should use the error type given to the observer', () => { + const observer = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + const options = queryClient.defaultQueryOptions< + { value: string }, + CustomError + >({ + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getOptimisticResult(options).error, + ).toEqualTypeOf() + }) + }) + + describe('getCurrentResult', () => { + it('should be typed from the queryFn', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getCurrentResult()).toEqualTypeOf< + QueryObserverResult<{ value: string }, DefaultError> + >() + }) + + it('should use the error type given to the observer', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + }) + + expectTypeOf( + observer.getCurrentResult().error, + ).toEqualTypeOf() + }) + }) + + describe('trackResult', () => { + it('should return the same result type it is given', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.trackResult(observer.getCurrentResult()), + ).toEqualTypeOf>() + }) + + it('should use the error type given to the observer', () => { + const observer = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf( + observer.trackResult(observer.getCurrentResult()).error, + ).toEqualTypeOf() + }) + + it('should type the tracked property in the onPropTracked callback', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + observer.trackResult(observer.getCurrentResult(), (key) => { + expectTypeOf(key).toEqualTypeOf() + }) + }) + }) + + describe('getCurrentQuery', () => { + it('should carry the data type into the query state', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getCurrentQuery().state.data).toEqualTypeOf< + { value: string } | undefined + >() + }) + + it('should carry the error type into the query state', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentQuery().state.error, + ).toEqualTypeOf() + }) + + it('should use the error type given to the observer', () => { + const observer = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf( + observer.getCurrentQuery().state.error, + ).toEqualTypeOf() + }) + + it('should keep the data type from before select', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ count: 1 }), + select: (data) => data.count, + }) + + expectTypeOf(observer.getCurrentQuery().state.data).toEqualTypeOf< + { count: number } | undefined + >() + }) + + it('should preserve a literal queryKey', () => { + const key = ['a', 1] as const + + const observer = new QueryObserver(queryClient, { + queryKey: key, + queryFn: () => Promise.resolve('data'), + }) + + expectTypeOf(observer.getCurrentQuery().queryKey).toEqualTypeOf< + readonly ['a', 1] + >() + }) + }) + + describe('refetch', () => { + it('should resolve with the observed result type', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.refetch()).toEqualTypeOf< + Promise> + >() + }) + + it('should use the error type given to the observer', () => { + const observer = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf< + Awaited>['error'] + >().toEqualTypeOf() + }) + + it('should only accept RefetchOptions', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.refetch).parameters.toEqualTypeOf< + [options?: RefetchOptions] + >() + }) + }) + + describe('fetchOptimistic', () => { + it('should keep the observed types in its options and result', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.fetchOptimistic).returns.toEqualTypeOf< + Promise> + >() + + observer.fetchOptimistic({ + queryKey: queryKey(), + // @ts-expect-error the queryFn must return the observed data type + queryFn: () => 42, + }) + }) + + it('should use the error type given to the observer', () => { + const observer = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf< + Awaited>['error'] + >().toEqualTypeOf() + }) + }) + describe('select', () => { + it('should type the result data as what select returns', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ count: 1 }), + select: (data) => data.count, + }) + + expectTypeOf(observer.getCurrentResult().data).toEqualTypeOf< + number | undefined + >() + }) + it('should infer the selected type in the subscribe callback', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), @@ -131,6 +997,12 @@ describe('queryObserver', () => { }) describe('placeholderData', () => { + it('should return the query data type or undefined', () => { + expectTypeOf< + PlaceholderDataFunction<{ value: string }> + >().returns.toEqualTypeOf<{ value: string } | undefined>() + }) + it('previousQuery should have typed queryKey', () => { const testQueryKey = ['SomeQuery', 42, { foo: 'bar' }] as const @@ -147,10 +1019,6 @@ describe('queryObserver', () => { }) it('previousQuery should have typed error', () => { - class CustomError extends Error { - name = 'CustomError' as const - } - new QueryObserver(new QueryClient(), { queryKey: queryKey(), placeholderData: (_, previousQuery) => { From c30560abb6254d5ed19676c26e1ce96b5da7e1fe Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 22:24:08 +0900 Subject: [PATCH 02/43] test(query-core/queryObserver): merge type tests that assert the same member --- .../src/__tests__/queryObserver.test-d.tsx | 192 ++++-------------- 1 file changed, 44 insertions(+), 148 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 6440f39d87..66097180df 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -118,123 +118,35 @@ describe('queryObserver', () => { }) describe('the result', () => { - it('should type dataUpdatedAt as a number', () => { + it('should type its timestamps and counters as numbers', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), }) - expectTypeOf( - observer.getCurrentResult().dataUpdatedAt, - ).toEqualTypeOf() - }) - - it('should type errorUpdatedAt as a number', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf( - observer.getCurrentResult().errorUpdatedAt, - ).toEqualTypeOf() - }) - - it('should type failureCount as a number', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf( - observer.getCurrentResult().failureCount, - ).toEqualTypeOf() - }) - - it('should type errorUpdateCount as a number', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf( - observer.getCurrentResult().errorUpdateCount, - ).toEqualTypeOf() - }) - - it('should type isFetching as a boolean', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf( - observer.getCurrentResult().isFetching, - ).toEqualTypeOf() - }) - - it('should type isRefetching as a boolean', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf( - observer.getCurrentResult().isRefetching, - ).toEqualTypeOf() - }) - - it('should type isPaused as a boolean', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf( - observer.getCurrentResult().isPaused, - ).toEqualTypeOf() - }) - - it('should type isStale as a boolean', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf(observer.getCurrentResult().isStale).toEqualTypeOf() - }) - - it('should type isEnabled as a boolean', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) + const result = observer.getCurrentResult() - expectTypeOf( - observer.getCurrentResult().isEnabled, - ).toEqualTypeOf() + expectTypeOf(result.dataUpdatedAt).toEqualTypeOf() + expectTypeOf(result.errorUpdatedAt).toEqualTypeOf() + expectTypeOf(result.failureCount).toEqualTypeOf() + expectTypeOf(result.errorUpdateCount).toEqualTypeOf() }) - it('should type isFetched as a boolean', () => { + it('should type its state flags as booleans', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), }) - expectTypeOf( - observer.getCurrentResult().isFetched, - ).toEqualTypeOf() - }) - - it('should type isFetchedAfterMount as a boolean', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) + const result = observer.getCurrentResult() - expectTypeOf( - observer.getCurrentResult().isFetchedAfterMount, - ).toEqualTypeOf() + expectTypeOf(result.isFetching).toEqualTypeOf() + expectTypeOf(result.isRefetching).toEqualTypeOf() + expectTypeOf(result.isPaused).toEqualTypeOf() + expectTypeOf(result.isStale).toEqualTypeOf() + expectTypeOf(result.isEnabled).toEqualTypeOf() + expectTypeOf(result.isFetched).toEqualTypeOf() + expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() }) it('should type failureReason from the error type', () => { @@ -734,10 +646,8 @@ describe('queryObserver', () => { expectTypeOf(observer.getOptimisticResult(options)).toEqualTypeOf< QueryObserverResult<{ value: string }, DefaultError> >() - }) - it('should use the error type given to the observer', () => { - const observer = new QueryObserver<{ value: string }, CustomError>( + const withCustomError = new QueryObserver<{ value: string }, CustomError>( queryClient, { queryKey: queryKey(), @@ -745,7 +655,7 @@ describe('queryObserver', () => { }, ) - const options = queryClient.defaultQueryOptions< + const customErrorOptions = queryClient.defaultQueryOptions< { value: string }, CustomError >({ @@ -754,13 +664,13 @@ describe('queryObserver', () => { }) expectTypeOf( - observer.getOptimisticResult(options).error, - ).toEqualTypeOf() + withCustomError.getOptimisticResult(customErrorOptions), + ).toEqualTypeOf>() }) }) describe('getCurrentResult', () => { - it('should be typed from the queryFn', () => { + it('should be typed from the observed types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -769,16 +679,18 @@ describe('queryObserver', () => { expectTypeOf(observer.getCurrentResult()).toEqualTypeOf< QueryObserverResult<{ value: string }, DefaultError> >() - }) - it('should use the error type given to the observer', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - }) + const withCustomError = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) - expectTypeOf( - observer.getCurrentResult().error, - ).toEqualTypeOf() + expectTypeOf(withCustomError.getCurrentResult()).toEqualTypeOf< + QueryObserverResult<{ value: string }, CustomError> + >() }) }) @@ -792,10 +704,8 @@ describe('queryObserver', () => { expectTypeOf( observer.trackResult(observer.getCurrentResult()), ).toEqualTypeOf>() - }) - it('should use the error type given to the observer', () => { - const observer = new QueryObserver<{ value: string }, CustomError>( + const withCustomError = new QueryObserver<{ value: string }, CustomError>( queryClient, { queryKey: queryKey(), @@ -804,8 +714,8 @@ describe('queryObserver', () => { ) expectTypeOf( - observer.trackResult(observer.getCurrentResult()).error, - ).toEqualTypeOf() + withCustomError.trackResult(withCustomError.getCurrentResult()), + ).toEqualTypeOf>() }) it('should type the tracked property in the onPropTracked callback', () => { @@ -821,7 +731,7 @@ describe('queryObserver', () => { }) describe('getCurrentQuery', () => { - it('should carry the data type into the query state', () => { + it('should carry the observed types into the query state', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -830,21 +740,11 @@ describe('queryObserver', () => { expectTypeOf(observer.getCurrentQuery().state.data).toEqualTypeOf< { value: string } | undefined >() - }) - - it('should carry the error type into the query state', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - expectTypeOf( observer.getCurrentQuery().state.error, ).toEqualTypeOf() - }) - it('should use the error type given to the observer', () => { - const observer = new QueryObserver<{ value: string }, CustomError>( + const withCustomError = new QueryObserver<{ value: string }, CustomError>( queryClient, { queryKey: queryKey(), @@ -853,7 +753,7 @@ describe('queryObserver', () => { ) expectTypeOf( - observer.getCurrentQuery().state.error, + withCustomError.getCurrentQuery().state.error, ).toEqualTypeOf() }) @@ -893,10 +793,8 @@ describe('queryObserver', () => { expectTypeOf(observer.refetch()).toEqualTypeOf< Promise> >() - }) - it('should use the error type given to the observer', () => { - const observer = new QueryObserver<{ value: string }, CustomError>( + const withCustomError = new QueryObserver<{ value: string }, CustomError>( queryClient, { queryKey: queryKey(), @@ -904,9 +802,9 @@ describe('queryObserver', () => { }, ) - expectTypeOf< - Awaited>['error'] - >().toEqualTypeOf() + expectTypeOf(withCustomError.refetch()).toEqualTypeOf< + Promise> + >() }) it('should only accept RefetchOptions', () => { @@ -937,10 +835,8 @@ describe('queryObserver', () => { // @ts-expect-error the queryFn must return the observed data type queryFn: () => 42, }) - }) - it('should use the error type given to the observer', () => { - const observer = new QueryObserver<{ value: string }, CustomError>( + const withCustomError = new QueryObserver<{ value: string }, CustomError>( queryClient, { queryKey: queryKey(), @@ -948,9 +844,9 @@ describe('queryObserver', () => { }, ) - expectTypeOf< - Awaited>['error'] - >().toEqualTypeOf() + expectTypeOf(withCustomError.fetchOptimistic).returns.toEqualTypeOf< + Promise> + >() }) }) From 0cfa5b4590668df91d188b23b4fd04198d26f52e Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 22:27:22 +0900 Subject: [PATCH 03/43] test(query-core/queryObserver): merge the 'queryFn' context assertions into one test --- .../src/__tests__/queryObserver.test-d.tsx | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 66097180df..d96dff5c6e 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -296,32 +296,14 @@ describe('queryObserver', () => { }) describe('the options', () => { - it('should type the queryKey given to the queryFn', () => { + it('should type the context given to the queryFn', () => { const key = ['a', 1] as const new QueryObserver(queryClient, { queryKey: key, queryFn: (context) => { expectTypeOf(context.queryKey).toEqualTypeOf() - return Promise.resolve('data') - }, - }) - }) - - it('should type the signal given to the queryFn', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: (context) => { expectTypeOf(context.signal).toEqualTypeOf() - return Promise.resolve('data') - }, - }) - }) - - it('should type the client given to the queryFn', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: (context) => { expectTypeOf(context.client).toEqualTypeOf() return Promise.resolve('data') }, From b397fb30ac18e3c515d0df4d290153cedf5bdc6a Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 22:30:59 +0900 Subject: [PATCH 04/43] test(query-core/queryObserver): group its type tests by the member they assert --- .../src/__tests__/queryObserver.test-d.tsx | 962 +++++++++--------- 1 file changed, 486 insertions(+), 476 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index d96dff5c6e..8a3d21b715 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -117,499 +117,376 @@ describe('queryObserver', () => { } }) - describe('the result', () => { - it('should type its timestamps and counters as numbers', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - expectTypeOf(result.dataUpdatedAt).toEqualTypeOf() - expectTypeOf(result.errorUpdatedAt).toEqualTypeOf() - expectTypeOf(result.failureCount).toEqualTypeOf() - expectTypeOf(result.errorUpdateCount).toEqualTypeOf() - }) - - it('should type its state flags as booleans', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - expectTypeOf(result.isFetching).toEqualTypeOf() - expectTypeOf(result.isRefetching).toEqualTypeOf() - expectTypeOf(result.isPaused).toEqualTypeOf() - expectTypeOf(result.isStale).toEqualTypeOf() - expectTypeOf(result.isEnabled).toEqualTypeOf() - expectTypeOf(result.isFetched).toEqualTypeOf() - expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() - }) - - it('should type failureReason from the error type', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), + describe('constructor', () => { + describe('queryFn', () => { + it('should type the context given to the queryFn', () => { + const key = ['a', 1] as const + + new QueryObserver(queryClient, { + queryKey: key, + queryFn: (context) => { + expectTypeOf(context.queryKey).toEqualTypeOf() + expectTypeOf(context.signal).toEqualTypeOf() + expectTypeOf(context.client).toEqualTypeOf() + return Promise.resolve('data') + }, + }) + }) + }) + + describe('callbacks', () => { + it('should type the query given to an enabled callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + enabled: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) }) - expectTypeOf( - observer.getCurrentResult().failureReason, - ).toEqualTypeOf() - }) - - it('should type its refetch from the observed types', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the query given to a staleTime callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + staleTime: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return 0 + }, + }) }) - expectTypeOf(observer.getCurrentResult().refetch).returns.toEqualTypeOf< - Promise> - >() - }) - - it('should only accept RefetchOptions in its refetch', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the query given to a refetchInterval callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchInterval: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return false + }, + }) }) - expectTypeOf( - observer.getCurrentResult().refetch, - ).parameters.toEqualTypeOf<[options?: RefetchOptions]>() - }) - - it('should type status as the query status union', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the query given to a refetchOnWindowFocus callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchOnWindowFocus: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) }) - expectTypeOf( - observer.getCurrentResult().status, - ).toEqualTypeOf() - }) - - it('should type fetchStatus as the fetch status union', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the query given to a refetchOnReconnect callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchOnReconnect: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) }) - expectTypeOf( - observer.getCurrentResult().fetchStatus, - ).toEqualTypeOf() - }) - }) - - describe('narrowing the result', () => { - it('should narrow error to the error type on an isError check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the query given to a refetchOnMount callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + refetchOnMount: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) }) - const result = observer.getCurrentResult() - - if (result.isError) { - expectTypeOf(result.error).toEqualTypeOf() - } - }) - - it('should keep data possibly undefined on an isError check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the query given to a retryOnMount callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + retryOnMount: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return true + }, + }) }) - const result = observer.getCurrentResult() - - if (result.isError) { - expectTypeOf(result.data).toEqualTypeOf<{ value: string } | undefined>() - } - }) - - it('should narrow data to be defined on a success status check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the error given to a throwOnError callback', () => { + new QueryObserver<{ value: string }, CustomError>(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + throwOnError: (error) => { + expectTypeOf(error).toEqualTypeOf() + return false + }, + }) }) - const result = observer.getCurrentResult() - - if (result.status === 'success') { - expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() - } - }) - - it('should narrow error to the error type on an error status check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the error given to a retry callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + retry: (_failureCount, error) => { + expectTypeOf(error).toEqualTypeOf() + return false + }, + }) }) - const result = observer.getCurrentResult() - - if (result.status === 'error') { - expectTypeOf(result.error).toEqualTypeOf() - } - }) - - it('should narrow data to undefined on a pending status check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type the error given to a retryDelay callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + retryDelay: (_failureCount, error) => { + expectTypeOf(error).toEqualTypeOf() + return 0 + }, + }) }) - const result = observer.getCurrentResult() - - if (result.status === 'pending') { - expectTypeOf(result.data).toEqualTypeOf() - } - }) - }) + it('should type the queryKey given to a queryKeyHashFn', () => { + const key = ['a', 1] as const - describe('setOptions', () => { - it('should keep the observed data type in the options it is given', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + new QueryObserver(queryClient, { + queryKey: key, + queryFn: () => Promise.resolve('data'), + queryKeyHashFn: (queryKeyToHash) => { + expectTypeOf(queryKeyToHash).toEqualTypeOf() + return 'hash' + }, + }) }) - observer.setOptions({ - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - select: (data) => { - expectTypeOf(data).toEqualTypeOf<{ value: string }>() - return data - }, - }) - - observer.setOptions({ - queryKey: queryKey(), - // @ts-expect-error the queryFn must return the observed data type - queryFn: () => 42, + it('should type a structuralSharing callback as unknown', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + structuralSharing: (_oldData, newData) => { + expectTypeOf(newData).toEqualTypeOf() + return newData + }, + }) }) }) - }) - describe('the options', () => { - it('should type the context given to the queryFn', () => { - const key = ['a', 1] as const + describe('select', () => { + it('should type the result data as what select returns', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ count: 1 }), + select: (data) => data.count, + }) - new QueryObserver(queryClient, { - queryKey: key, - queryFn: (context) => { - expectTypeOf(context.queryKey).toEqualTypeOf() - expectTypeOf(context.signal).toEqualTypeOf() - expectTypeOf(context.client).toEqualTypeOf() - return Promise.resolve('data') - }, + expectTypeOf(observer.getCurrentResult().data).toEqualTypeOf< + number | undefined + >() }) - }) - it('should type the query given to an enabled callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - enabled: (query) => { - expectTypeOf(query.state.data).toEqualTypeOf< - { value: string } | undefined - >() - return true - }, - }) - }) + it('should infer the selected type in the subscribe callback', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => ({ count: 1 }), + select: (data) => ({ myCount: data.count }), + }) - it('should type the query given to a staleTime callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - staleTime: (query) => { - expectTypeOf(query.state.data).toEqualTypeOf< - { value: string } | undefined + observer.subscribe((result) => { + expectTypeOf(result).toEqualTypeOf< + QueryObserverResult<{ myCount: number }> >() - return 0 - }, + }) }) - }) - it('should type the query given to a refetchInterval callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - refetchInterval: (query) => { - expectTypeOf(query.state.data).toEqualTypeOf< - { value: string } | undefined - >() - return false - }, - }) - }) + it('should infer the selected type from the refetch result', async () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => ({ count: 1 }), + select: (data) => ({ myCount: data.count }), + }) - it('should type the query given to a refetchOnWindowFocus callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - refetchOnWindowFocus: (query) => { - expectTypeOf(query.state.data).toEqualTypeOf< - { value: string } | undefined - >() - return true - }, - }) - }) + const observerResult = await observer.refetch() - it('should type the query given to a refetchOnReconnect callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - refetchOnReconnect: (query) => { - expectTypeOf(query.state.data).toEqualTypeOf< - { value: string } | undefined - >() - return true - }, + expectTypeOf(observerResult.data).toEqualTypeOf< + { myCount: number } | undefined + >() }) }) - it('should type the query given to a refetchOnMount callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - refetchOnMount: (query) => { - expectTypeOf(query.state.data).toEqualTypeOf< - { value: string } | undefined - >() - return true - }, + describe('initialData', () => { + it('should type an initialData function from the data type', () => { + expectTypeOf< + InitialDataFunction<{ value: string }> + >().returns.toEqualTypeOf<{ value: string } | undefined>() }) - }) - it('should type the query given to a retryOnMount callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - retryOnMount: (query) => { - expectTypeOf(query.state.data).toEqualTypeOf< - { value: string } | undefined - >() - return true - }, + it('should accept a function for initialDataUpdatedAt', () => { + expectTypeOf< + QueryObserverOptions['initialDataUpdatedAt'] + >().toEqualTypeOf number | undefined) | undefined>() }) }) - it('should type persister from the queryFn data', () => { - expectTypeOf< - QueryObserverOptions<{ value: string }>['persister'] - >().toEqualTypeOf< - | QueryPersister<{ value: string }, ReadonlyArray, never> - | undefined - >() - }) - - it('should type meta as its named type', () => { - expectTypeOf().toEqualTypeOf< - QueryMeta | undefined - >() - }) - - it('should type notifyOnChangeProps as its named type', () => { - expectTypeOf().toEqualTypeOf< - NotifyOnChangeProps | undefined - >() - }) - - it('should type the error given to a throwOnError callback', () => { - new QueryObserver<{ value: string }, CustomError>(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - throwOnError: (error) => { - expectTypeOf(error).toEqualTypeOf() - return false - }, + describe('placeholderData', () => { + it('should return the query data type or undefined', () => { + expectTypeOf< + PlaceholderDataFunction<{ value: string }> + >().returns.toEqualTypeOf<{ value: string } | undefined>() }) - }) - it('should type suspense as a boolean', () => { - expectTypeOf().toEqualTypeOf< - boolean | undefined - >() - }) + it('previousQuery should have typed queryKey', () => { + const testQueryKey = ['SomeQuery', 42, { foo: 'bar' }] as const - it('should type refetchIntervalInBackground as a boolean', () => { - expectTypeOf< - QueryObserverOptions['refetchIntervalInBackground'] - >().toEqualTypeOf() - }) - - it('should reject a non-number gcTime', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve('data'), - // @ts-expect-error gcTime must be a number - gcTime: 'nope', + new QueryObserver(new QueryClient(), { + queryKey: testQueryKey, + placeholderData: (_, previousQuery) => { + if (previousQuery) { + expectTypeOf(previousQuery.queryKey).toEqualTypeOf< + typeof testQueryKey + >() + } + }, + }) }) - expectTypeOf().toEqualTypeOf< - number | undefined - >() - }) - - it('should type networkMode as the network mode union', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve('data'), - // @ts-expect-error networkMode must be one of the NetworkMode values - networkMode: 'nope', + it('previousQuery should have typed error', () => { + new QueryObserver(new QueryClient(), { + queryKey: queryKey(), + placeholderData: (_, previousQuery) => { + if (previousQuery) { + expectTypeOf( + previousQuery.state.error, + ).toEqualTypeOf() + } + return undefined + }, + }) }) - expectTypeOf().toEqualTypeOf< - NetworkMode | undefined - >() - }) + it('previousData should have the same type as query data', () => { + const queryData = { foo: 'bar' } as const - it('should reject a non-string queryHash', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve('data'), - // @ts-expect-error queryHash must be a string - queryHash: 42, + new QueryObserver(new QueryClient(), { + queryKey: queryKey(), + queryFn: () => queryData, + select: (data) => data.foo, + placeholderData: (previousData) => { + expectTypeOf(previousData).toEqualTypeOf< + typeof queryData | undefined + >() + return undefined + }, + }) }) - - expectTypeOf().toEqualTypeOf< - string | undefined - >() - }) - - it('should accept a function for initialDataUpdatedAt', () => { - expectTypeOf< - QueryObserverOptions['initialDataUpdatedAt'] - >().toEqualTypeOf number | undefined) | undefined>() }) - it('should type the error given to a retry callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - retry: (_failureCount, error) => { - expectTypeOf(error).toEqualTypeOf() - return false - }, - }) - }) + describe('plain options', () => { + it('should reject a non-number gcTime', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error gcTime must be a number + gcTime: 'nope', + }) - it('should type the error given to a retryDelay callback', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - retryDelay: (_failureCount, error) => { - expectTypeOf(error).toEqualTypeOf() - return 0 - }, + expectTypeOf().toEqualTypeOf< + number | undefined + >() }) - }) - it('should type the queryKey given to a queryKeyHashFn', () => { - const key = ['a', 1] as const + it('should reject a non-string queryHash', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error queryHash must be a string + queryHash: 42, + }) - new QueryObserver(queryClient, { - queryKey: key, - queryFn: () => Promise.resolve('data'), - queryKeyHashFn: (queryKeyToHash) => { - expectTypeOf(queryKeyToHash).toEqualTypeOf() - return 'hash' - }, + expectTypeOf().toEqualTypeOf< + string | undefined + >() }) - }) - it('should type an initialData function from the data type', () => { - expectTypeOf< - InitialDataFunction<{ value: string }> - >().returns.toEqualTypeOf<{ value: string } | undefined>() - }) + it('should type networkMode as the network mode union', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error networkMode must be one of the NetworkMode values + networkMode: 'nope', + }) - it('should type a structuralSharing callback as unknown', () => { - new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - structuralSharing: (_oldData, newData) => { - expectTypeOf(newData).toEqualTypeOf() - return newData - }, + expectTypeOf().toEqualTypeOf< + NetworkMode | undefined + >() }) - }) - }) - describe('destroy', () => { - it('should return void', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type suspense as a boolean', () => { + expectTypeOf().toEqualTypeOf< + boolean | undefined + >() }) - expectTypeOf(observer.destroy).returns.toEqualTypeOf() - }) - }) - - describe('updateResult', () => { - it('should return void', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type refetchIntervalInBackground as a boolean', () => { + expectTypeOf< + QueryObserverOptions['refetchIntervalInBackground'] + >().toEqualTypeOf() }) - expectTypeOf(observer.updateResult).returns.toEqualTypeOf() - }) - }) + it('should type meta as its named type', () => { + expectTypeOf().toEqualTypeOf< + QueryMeta | undefined + >() + }) - describe('onQueryUpdate', () => { - it('should return void', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + it('should type notifyOnChangeProps as its named type', () => { + expectTypeOf< + QueryObserverOptions['notifyOnChangeProps'] + >().toEqualTypeOf() }) - expectTypeOf(observer.onQueryUpdate).returns.toEqualTypeOf() + it('should type persister from the queryFn data', () => { + expectTypeOf< + QueryObserverOptions<{ value: string }>['persister'] + >().toEqualTypeOf< + | QueryPersister<{ value: string }, ReadonlyArray, never> + | undefined + >() + }) }) }) - describe('shouldFetchOnReconnect', () => { - it('should return a boolean', () => { + describe('setOptions', () => { + it('should keep the observed data type in the options it is given', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), }) - expectTypeOf( - observer.shouldFetchOnReconnect, - ).returns.toEqualTypeOf() - }) - }) - - describe('shouldFetchOnWindowFocus', () => { - it('should return a boolean', () => { - const observer = new QueryObserver(queryClient, { + observer.setOptions({ queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), + select: (data) => { + expectTypeOf(data).toEqualTypeOf<{ value: string }>() + return data + }, }) - expectTypeOf( - observer.shouldFetchOnWindowFocus, - ).returns.toEqualTypeOf() - }) - }) - - describe('trackProp', () => { - it('should only accept a key of the result', () => { - const observer = new QueryObserver(queryClient, { + observer.setOptions({ queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + // @ts-expect-error the queryFn must return the observed data type + queryFn: () => 42, }) - - expectTypeOf(observer.trackProp).parameters.toEqualTypeOf< - [key: keyof QueryObserverResult] - >() }) }) @@ -674,6 +551,162 @@ describe('queryObserver', () => { QueryObserverResult<{ value: string }, CustomError> >() }) + + describe('properties', () => { + it('should type its timestamps and counters as numbers', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + expectTypeOf(result.dataUpdatedAt).toEqualTypeOf() + expectTypeOf(result.errorUpdatedAt).toEqualTypeOf() + expectTypeOf(result.failureCount).toEqualTypeOf() + expectTypeOf(result.errorUpdateCount).toEqualTypeOf() + }) + + it('should type its state flags as booleans', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + expectTypeOf(result.isFetching).toEqualTypeOf() + expectTypeOf(result.isRefetching).toEqualTypeOf() + expectTypeOf(result.isPaused).toEqualTypeOf() + expectTypeOf(result.isStale).toEqualTypeOf() + expectTypeOf(result.isEnabled).toEqualTypeOf() + expectTypeOf(result.isFetched).toEqualTypeOf() + expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() + }) + + it('should type failureReason from the error type', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + }) + + expectTypeOf( + observer.getCurrentResult().failureReason, + ).toEqualTypeOf() + }) + + it('should type its refetch from the observed types', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getCurrentResult().refetch).returns.toEqualTypeOf< + Promise> + >() + }) + + it('should only accept RefetchOptions in its refetch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().refetch, + ).parameters.toEqualTypeOf<[options?: RefetchOptions]>() + }) + + it('should type status as the query status union', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().status, + ).toEqualTypeOf() + }) + + it('should type fetchStatus as the fetch status union', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().fetchStatus, + ).toEqualTypeOf() + }) + }) + + describe('narrowing', () => { + it('should narrow error to the error type on an isError check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isError) { + expectTypeOf(result.error).toEqualTypeOf() + } + }) + + it('should keep data possibly undefined on an isError check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isError) { + expectTypeOf(result.data).toEqualTypeOf< + { value: string } | undefined + >() + } + }) + + it('should narrow data to be defined on a success status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'success') { + expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() + } + }) + + it('should narrow error to the error type on an error status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'error') { + expectTypeOf(result.error).toEqualTypeOf() + } + }) + + it('should narrow data to undefined on a pending status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'pending') { + expectTypeOf(result.data).toEqualTypeOf() + } + }) + }) }) describe('trackResult', () => { @@ -712,6 +745,19 @@ describe('queryObserver', () => { }) }) + describe('trackProp', () => { + it('should only accept a key of the result', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.trackProp).parameters.toEqualTypeOf< + [key: keyof QueryObserverResult] + >() + }) + }) + describe('getCurrentQuery', () => { it('should carry the observed types into the query state', () => { const observer = new QueryObserver(queryClient, { @@ -832,98 +878,62 @@ describe('queryObserver', () => { }) }) - describe('select', () => { - it('should type the result data as what select returns', () => { + describe('destroy', () => { + it('should return void', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), - queryFn: () => Promise.resolve({ count: 1 }), - select: (data) => data.count, + queryFn: () => Promise.resolve({ value: 'data' }), }) - expectTypeOf(observer.getCurrentResult().data).toEqualTypeOf< - number | undefined - >() + expectTypeOf(observer.destroy).returns.toEqualTypeOf() }) + }) - it('should infer the selected type in the subscribe callback', () => { + describe('updateResult', () => { + it('should return void', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), - queryFn: () => ({ count: 1 }), - select: (data) => ({ myCount: data.count }), + queryFn: () => Promise.resolve({ value: 'data' }), }) - observer.subscribe((result) => { - expectTypeOf(result).toEqualTypeOf< - QueryObserverResult<{ myCount: number }> - >() - }) + expectTypeOf(observer.updateResult).returns.toEqualTypeOf() }) + }) - it('should infer the selected type from the refetch result', async () => { + describe('onQueryUpdate', () => { + it('should return void', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), - queryFn: () => ({ count: 1 }), - select: (data) => ({ myCount: data.count }), + queryFn: () => Promise.resolve({ value: 'data' }), }) - const observerResult = await observer.refetch() - - expectTypeOf(observerResult.data).toEqualTypeOf< - { myCount: number } | undefined - >() + expectTypeOf(observer.onQueryUpdate).returns.toEqualTypeOf() }) }) - describe('placeholderData', () => { - it('should return the query data type or undefined', () => { - expectTypeOf< - PlaceholderDataFunction<{ value: string }> - >().returns.toEqualTypeOf<{ value: string } | undefined>() - }) - - it('previousQuery should have typed queryKey', () => { - const testQueryKey = ['SomeQuery', 42, { foo: 'bar' }] as const - - new QueryObserver(new QueryClient(), { - queryKey: testQueryKey, - placeholderData: (_, previousQuery) => { - if (previousQuery) { - expectTypeOf(previousQuery.queryKey).toEqualTypeOf< - typeof testQueryKey - >() - } - }, - }) - }) - - it('previousQuery should have typed error', () => { - new QueryObserver(new QueryClient(), { + describe('shouldFetchOnReconnect', () => { + it('should return a boolean', () => { + const observer = new QueryObserver(queryClient, { queryKey: queryKey(), - placeholderData: (_, previousQuery) => { - if (previousQuery) { - expectTypeOf( - previousQuery.state.error, - ).toEqualTypeOf() - } - return undefined - }, + queryFn: () => Promise.resolve({ value: 'data' }), }) - }) - it('previousData should have the same type as query data', () => { - const queryData = { foo: 'bar' } as const + expectTypeOf( + observer.shouldFetchOnReconnect, + ).returns.toEqualTypeOf() + }) + }) - new QueryObserver(new QueryClient(), { + describe('shouldFetchOnWindowFocus', () => { + it('should return a boolean', () => { + const observer = new QueryObserver(queryClient, { queryKey: queryKey(), - queryFn: () => queryData, - select: (data) => data.foo, - placeholderData: (previousData) => { - expectTypeOf(previousData).toEqualTypeOf< - typeof queryData | undefined - >() - return undefined - }, + queryFn: () => Promise.resolve({ value: 'data' }), }) + + expectTypeOf( + observer.shouldFetchOnWindowFocus, + ).returns.toEqualTypeOf() }) }) }) From 0cb4e3062f19d9a9e3d49270e930ce20ecdbe241 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 22:33:25 +0900 Subject: [PATCH 05/43] test(query-core/queryObserver): group its type tests by the type they assert --- .../src/__tests__/queryObserver.test-d.tsx | 182 +++++++++--------- 1 file changed, 92 insertions(+), 90 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 8a3d21b715..51f8945f2f 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -117,7 +117,7 @@ describe('queryObserver', () => { } }) - describe('constructor', () => { + describe('QueryObserverOptions', () => { describe('queryFn', () => { it('should type the context given to the queryFn', () => { const key = ['a', 1] as const @@ -345,7 +345,7 @@ describe('queryObserver', () => { >().returns.toEqualTypeOf<{ value: string } | undefined>() }) - it('previousQuery should have typed queryKey', () => { + it('should type the queryKey of the previousQuery it is given', () => { const testQueryKey = ['SomeQuery', 42, { foo: 'bar' }] as const new QueryObserver(new QueryClient(), { @@ -360,7 +360,7 @@ describe('queryObserver', () => { }) }) - it('previousQuery should have typed error', () => { + it('should type the error of the previousQuery it is given', () => { new QueryObserver(new QueryClient(), { queryKey: queryKey(), placeholderData: (_, previousQuery) => { @@ -374,7 +374,7 @@ describe('queryObserver', () => { }) }) - it('previousData should have the same type as query data', () => { + it('should type previousData as the query data', () => { const queryData = { foo: 'bar' } as const new QueryObserver(new QueryClient(), { @@ -466,92 +466,7 @@ describe('queryObserver', () => { }) }) - describe('setOptions', () => { - it('should keep the observed data type in the options it is given', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - observer.setOptions({ - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - select: (data) => { - expectTypeOf(data).toEqualTypeOf<{ value: string }>() - return data - }, - }) - - observer.setOptions({ - queryKey: queryKey(), - // @ts-expect-error the queryFn must return the observed data type - queryFn: () => 42, - }) - }) - }) - - describe('getOptimisticResult', () => { - it('should be typed from the options it is given', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const options = queryClient.defaultQueryOptions({ - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf(observer.getOptimisticResult(options)).toEqualTypeOf< - QueryObserverResult<{ value: string }, DefaultError> - >() - - const withCustomError = new QueryObserver<{ value: string }, CustomError>( - queryClient, - { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }, - ) - - const customErrorOptions = queryClient.defaultQueryOptions< - { value: string }, - CustomError - >({ - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf( - withCustomError.getOptimisticResult(customErrorOptions), - ).toEqualTypeOf>() - }) - }) - - describe('getCurrentResult', () => { - it('should be typed from the observed types', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - expectTypeOf(observer.getCurrentResult()).toEqualTypeOf< - QueryObserverResult<{ value: string }, DefaultError> - >() - - const withCustomError = new QueryObserver<{ value: string }, CustomError>( - queryClient, - { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }, - ) - - expectTypeOf(withCustomError.getCurrentResult()).toEqualTypeOf< - QueryObserverResult<{ value: string }, CustomError> - >() - }) - + describe('QueryObserverResult', () => { describe('properties', () => { it('should type its timestamps and counters as numbers', () => { const observer = new QueryObserver(queryClient, { @@ -709,6 +624,93 @@ describe('queryObserver', () => { }) }) + describe('setOptions', () => { + it('should keep the observed data type in the options it is given', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + observer.setOptions({ + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + select: (data) => { + expectTypeOf(data).toEqualTypeOf<{ value: string }>() + return data + }, + }) + + observer.setOptions({ + queryKey: queryKey(), + // @ts-expect-error the queryFn must return the observed data type + queryFn: () => 42, + }) + }) + }) + + describe('getOptimisticResult', () => { + it('should be typed from the options it is given', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const options = queryClient.defaultQueryOptions({ + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getOptimisticResult(options)).toEqualTypeOf< + QueryObserverResult<{ value: string }, DefaultError> + >() + + const withCustomError = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + const customErrorOptions = queryClient.defaultQueryOptions< + { value: string }, + CustomError + >({ + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + withCustomError.getOptimisticResult(customErrorOptions), + ).toEqualTypeOf>() + }) + }) + + describe('getCurrentResult', () => { + it('should be typed from the observed types', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.getCurrentResult()).toEqualTypeOf< + QueryObserverResult<{ value: string }, DefaultError> + >() + + const withCustomError = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf(withCustomError.getCurrentResult()).toEqualTypeOf< + QueryObserverResult<{ value: string }, CustomError> + >() + }) + }) + describe('trackResult', () => { it('should return the same result type it is given', () => { const observer = new QueryObserver(queryClient, { From 1c3f065e39d224523986c797e117693768cc701f Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 22:46:33 +0900 Subject: [PATCH 06/43] test(query-core/queryObserver): add type tests for the discriminant literals of each 'QueryObserverResult' branch --- .../src/__tests__/queryObserver.test-d.tsx | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 51f8945f2f..5a4f420681 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -622,6 +622,62 @@ describe('queryObserver', () => { } }) }) + + describe('discriminants', () => { + it('should fix isPending to true on the pending branch', () => { + type Pending = Extract< + QueryObserverResult<{ value: string }>, + { status: 'pending'; isLoading: boolean } + > + + expectTypeOf().toEqualTypeOf() + }) + + it('should fix isLoading to true on the loading branch', () => { + type Loading = Extract< + QueryObserverResult<{ value: string }>, + { isLoading: true } + > + + expectTypeOf().toEqualTypeOf() + }) + + it('should fix isSuccess to true on the success branch', () => { + type Success = Extract< + QueryObserverResult<{ value: string }>, + { status: 'success'; isPlaceholderData: false } + > + + expectTypeOf().toEqualTypeOf() + }) + + it('should fix isPlaceholderData to true on the placeholder branch', () => { + type Placeholder = Extract< + QueryObserverResult<{ value: string }>, + { isPlaceholderData: true } + > + + expectTypeOf().toEqualTypeOf() + }) + + it('should fix isLoadingError to true on the loading error branch', () => { + type LoadingError = Extract< + QueryObserverResult<{ value: string }>, + { isLoadingError: true } + > + + expectTypeOf().toEqualTypeOf() + }) + + it('should fix isRefetchError to true on the refetch error branch', () => { + type RefetchError = Extract< + QueryObserverResult<{ value: string }>, + { isRefetchError: true } + > + + expectTypeOf().toEqualTypeOf() + }) + }) }) describe('setOptions', () => { From 8b12aa49cde7ac7cf7b97434565a8ec382fdf260 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 22:54:05 +0900 Subject: [PATCH 07/43] test(query-core/queryObserver): split the result type test into one test per 'QueryObserverResult' branch --- .../src/__tests__/queryObserver.test-d.tsx | 218 +++++++++++------- 1 file changed, 133 insertions(+), 85 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 5a4f420681..ba5de41340 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -32,91 +32,6 @@ describe('queryObserver', () => { queryClient.clear() }) - it('should be inferred as a correct result type', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - if (result.isPending) { - expectTypeOf(result.data).toEqualTypeOf() - expectTypeOf(result.error).toEqualTypeOf() - expectTypeOf(result.isError).toEqualTypeOf() - expectTypeOf(result.isPending).toEqualTypeOf() - expectTypeOf(result.isLoading).toEqualTypeOf() - expectTypeOf(result.isLoadingError).toEqualTypeOf() - expectTypeOf(result.isRefetchError).toEqualTypeOf() - expectTypeOf(result.status).toEqualTypeOf<'pending'>() - expectTypeOf(result.isPlaceholderData).toEqualTypeOf() - } - if (result.isLoading) { - expectTypeOf(result.data).toEqualTypeOf() - expectTypeOf(result.error).toEqualTypeOf() - expectTypeOf(result.isError).toEqualTypeOf() - expectTypeOf(result.isPending).toEqualTypeOf() - expectTypeOf(result.isLoading).toEqualTypeOf() - expectTypeOf(result.isLoadingError).toEqualTypeOf() - expectTypeOf(result.isRefetchError).toEqualTypeOf() - expectTypeOf(result.isSuccess).toEqualTypeOf() - expectTypeOf(result.status).toEqualTypeOf<'pending'>() - expectTypeOf(result.isPlaceholderData).toEqualTypeOf() - } - - if (result.isLoadingError) { - expectTypeOf(result.data).toEqualTypeOf() - expectTypeOf(result.error).toEqualTypeOf() - expectTypeOf(result.isError).toEqualTypeOf() - expectTypeOf(result.isPending).toEqualTypeOf() - expectTypeOf(result.isLoading).toEqualTypeOf() - expectTypeOf(result.isLoadingError).toEqualTypeOf() - expectTypeOf(result.isRefetchError).toEqualTypeOf() - expectTypeOf(result.isSuccess).toEqualTypeOf() - expectTypeOf(result.status).toEqualTypeOf<'error'>() - expectTypeOf(result.isPlaceholderData).toEqualTypeOf() - } - - if (result.isRefetchError) { - expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() - expectTypeOf(result.error).toEqualTypeOf() - expectTypeOf(result.isError).toEqualTypeOf() - expectTypeOf(result.isPending).toEqualTypeOf() - expectTypeOf(result.isLoading).toEqualTypeOf() - expectTypeOf(result.isLoadingError).toEqualTypeOf() - expectTypeOf(result.isRefetchError).toEqualTypeOf() - expectTypeOf(result.isSuccess).toEqualTypeOf() - expectTypeOf(result.status).toEqualTypeOf<'error'>() - expectTypeOf(result.isPlaceholderData).toEqualTypeOf() - } - - if (result.isSuccess) { - expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() - expectTypeOf(result.error).toEqualTypeOf() - expectTypeOf(result.isError).toEqualTypeOf() - expectTypeOf(result.isPending).toEqualTypeOf() - expectTypeOf(result.isLoading).toEqualTypeOf() - expectTypeOf(result.isLoadingError).toEqualTypeOf() - expectTypeOf(result.isRefetchError).toEqualTypeOf() - expectTypeOf(result.isSuccess).toEqualTypeOf() - expectTypeOf(result.status).toEqualTypeOf<'success'>() - expectTypeOf(result.isPlaceholderData).toEqualTypeOf() - } - - if (result.isPlaceholderData) { - expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() - expectTypeOf(result.error).toEqualTypeOf() - expectTypeOf(result.isError).toEqualTypeOf() - expectTypeOf(result.isPending).toEqualTypeOf() - expectTypeOf(result.isLoading).toEqualTypeOf() - expectTypeOf(result.isLoadingError).toEqualTypeOf() - expectTypeOf(result.isRefetchError).toEqualTypeOf() - expectTypeOf(result.isSuccess).toEqualTypeOf() - expectTypeOf(result.status).toEqualTypeOf<'success'>() - expectTypeOf(result.isPlaceholderData).toEqualTypeOf() - } - }) - describe('QueryObserverOptions', () => { describe('queryFn', () => { it('should type the context given to the queryFn', () => { @@ -678,6 +593,139 @@ describe('queryObserver', () => { expectTypeOf().toEqualTypeOf() }) }) + + describe('branches', () => { + it('should type every flag on the pending branch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isPending) { + expectTypeOf(result.data).toEqualTypeOf() + expectTypeOf(result.error).toEqualTypeOf() + expectTypeOf(result.isError).toEqualTypeOf() + expectTypeOf(result.isPending).toEqualTypeOf() + expectTypeOf(result.isLoading).toEqualTypeOf() + expectTypeOf(result.isLoadingError).toEqualTypeOf() + expectTypeOf(result.isRefetchError).toEqualTypeOf() + expectTypeOf(result.status).toEqualTypeOf<'pending'>() + expectTypeOf(result.isPlaceholderData).toEqualTypeOf() + } + }) + + it('should type every flag on the loading branch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isLoading) { + expectTypeOf(result.data).toEqualTypeOf() + expectTypeOf(result.error).toEqualTypeOf() + expectTypeOf(result.isError).toEqualTypeOf() + expectTypeOf(result.isPending).toEqualTypeOf() + expectTypeOf(result.isLoading).toEqualTypeOf() + expectTypeOf(result.isLoadingError).toEqualTypeOf() + expectTypeOf(result.isRefetchError).toEqualTypeOf() + expectTypeOf(result.isSuccess).toEqualTypeOf() + expectTypeOf(result.status).toEqualTypeOf<'pending'>() + expectTypeOf(result.isPlaceholderData).toEqualTypeOf() + } + }) + + it('should type every flag on the loading error branch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isLoadingError) { + expectTypeOf(result.data).toEqualTypeOf() + expectTypeOf(result.error).toEqualTypeOf() + expectTypeOf(result.isError).toEqualTypeOf() + expectTypeOf(result.isPending).toEqualTypeOf() + expectTypeOf(result.isLoading).toEqualTypeOf() + expectTypeOf(result.isLoadingError).toEqualTypeOf() + expectTypeOf(result.isRefetchError).toEqualTypeOf() + expectTypeOf(result.isSuccess).toEqualTypeOf() + expectTypeOf(result.status).toEqualTypeOf<'error'>() + expectTypeOf(result.isPlaceholderData).toEqualTypeOf() + } + }) + + it('should type every flag on the refetch error branch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isRefetchError) { + expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() + expectTypeOf(result.error).toEqualTypeOf() + expectTypeOf(result.isError).toEqualTypeOf() + expectTypeOf(result.isPending).toEqualTypeOf() + expectTypeOf(result.isLoading).toEqualTypeOf() + expectTypeOf(result.isLoadingError).toEqualTypeOf() + expectTypeOf(result.isRefetchError).toEqualTypeOf() + expectTypeOf(result.isSuccess).toEqualTypeOf() + expectTypeOf(result.status).toEqualTypeOf<'error'>() + expectTypeOf(result.isPlaceholderData).toEqualTypeOf() + } + }) + + it('should type every flag on the success branch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isSuccess) { + expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() + expectTypeOf(result.error).toEqualTypeOf() + expectTypeOf(result.isError).toEqualTypeOf() + expectTypeOf(result.isPending).toEqualTypeOf() + expectTypeOf(result.isLoading).toEqualTypeOf() + expectTypeOf(result.isLoadingError).toEqualTypeOf() + expectTypeOf(result.isRefetchError).toEqualTypeOf() + expectTypeOf(result.isSuccess).toEqualTypeOf() + expectTypeOf(result.status).toEqualTypeOf<'success'>() + expectTypeOf(result.isPlaceholderData).toEqualTypeOf() + } + }) + + it('should type every flag on the placeholder branch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isPlaceholderData) { + expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() + expectTypeOf(result.error).toEqualTypeOf() + expectTypeOf(result.isError).toEqualTypeOf() + expectTypeOf(result.isPending).toEqualTypeOf() + expectTypeOf(result.isLoading).toEqualTypeOf() + expectTypeOf(result.isLoadingError).toEqualTypeOf() + expectTypeOf(result.isRefetchError).toEqualTypeOf() + expectTypeOf(result.isSuccess).toEqualTypeOf() + expectTypeOf(result.status).toEqualTypeOf<'success'>() + expectTypeOf(result.isPlaceholderData).toEqualTypeOf() + } + }) + }) }) describe('setOptions', () => { From ead11ec5269726fb2e56546e6cfd949fa7f21cd6 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:03:40 +0900 Subject: [PATCH 08/43] test(query-core/queryObserver): group its type tests by the option and result member they assert --- .../src/__tests__/queryObserver.test-d.tsx | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index ba5de41340..7ea59de975 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -49,7 +49,7 @@ describe('queryObserver', () => { }) }) - describe('callbacks', () => { + describe('enabled', () => { it('should type the query given to an enabled callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -62,7 +62,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('staleTime', () => { it('should type the query given to a staleTime callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -75,7 +77,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('refetchInterval', () => { it('should type the query given to a refetchInterval callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -88,7 +92,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('refetchOnWindowFocus', () => { it('should type the query given to a refetchOnWindowFocus callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -101,7 +107,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('refetchOnReconnect', () => { it('should type the query given to a refetchOnReconnect callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -114,7 +122,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('refetchOnMount', () => { it('should type the query given to a refetchOnMount callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -127,7 +137,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('retryOnMount', () => { it('should type the query given to a retryOnMount callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -140,7 +152,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('throwOnError', () => { it('should type the error given to a throwOnError callback', () => { new QueryObserver<{ value: string }, CustomError>(queryClient, { queryKey: queryKey(), @@ -151,7 +165,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('retry', () => { it('should type the error given to a retry callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -161,7 +177,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('retryDelay', () => { it('should type the error given to a retryDelay callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -171,7 +189,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('queryKeyHashFn', () => { it('should type the queryKey given to a queryKeyHashFn', () => { const key = ['a', 1] as const @@ -184,7 +204,9 @@ describe('queryObserver', () => { }, }) }) + }) + describe('structuralSharing', () => { it('should type a structuralSharing callback as unknown', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -245,7 +267,9 @@ describe('queryObserver', () => { InitialDataFunction<{ value: string }> >().returns.toEqualTypeOf<{ value: string } | undefined>() }) + }) + describe('initialDataUpdatedAt', () => { it('should accept a function for initialDataUpdatedAt', () => { expectTypeOf< QueryObserverOptions['initialDataUpdatedAt'] @@ -306,7 +330,7 @@ describe('queryObserver', () => { }) }) - describe('plain options', () => { + describe('gcTime', () => { it('should reject a non-number gcTime', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -319,7 +343,9 @@ describe('queryObserver', () => { number | undefined >() }) + }) + describe('queryHash', () => { it('should reject a non-string queryHash', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -332,7 +358,9 @@ describe('queryObserver', () => { string | undefined >() }) + }) + describe('networkMode', () => { it('should type networkMode as the network mode union', () => { new QueryObserver(queryClient, { queryKey: queryKey(), @@ -345,31 +373,41 @@ describe('queryObserver', () => { NetworkMode | undefined >() }) + }) + describe('suspense', () => { it('should type suspense as a boolean', () => { expectTypeOf().toEqualTypeOf< boolean | undefined >() }) + }) + describe('refetchIntervalInBackground', () => { it('should type refetchIntervalInBackground as a boolean', () => { expectTypeOf< QueryObserverOptions['refetchIntervalInBackground'] >().toEqualTypeOf() }) + }) + describe('meta', () => { it('should type meta as its named type', () => { expectTypeOf().toEqualTypeOf< QueryMeta | undefined >() }) + }) + describe('notifyOnChangeProps', () => { it('should type notifyOnChangeProps as its named type', () => { expectTypeOf< QueryObserverOptions['notifyOnChangeProps'] >().toEqualTypeOf() }) + }) + describe('persister', () => { it('should type persister from the queryFn data', () => { expectTypeOf< QueryObserverOptions<{ value: string }>['persister'] @@ -382,7 +420,7 @@ describe('queryObserver', () => { }) describe('QueryObserverResult', () => { - describe('properties', () => { + describe('timestamps and counters', () => { it('should type its timestamps and counters as numbers', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), @@ -396,7 +434,9 @@ describe('queryObserver', () => { expectTypeOf(result.failureCount).toEqualTypeOf() expectTypeOf(result.errorUpdateCount).toEqualTypeOf() }) + }) + describe('state flags', () => { it('should type its state flags as booleans', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), @@ -413,7 +453,9 @@ describe('queryObserver', () => { expectTypeOf(result.isFetched).toEqualTypeOf() expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() }) + }) + describe('failureReason', () => { it('should type failureReason from the error type', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), @@ -423,7 +465,9 @@ describe('queryObserver', () => { observer.getCurrentResult().failureReason, ).toEqualTypeOf() }) + }) + describe('refetch', () => { it('should type its refetch from the observed types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), @@ -445,7 +489,9 @@ describe('queryObserver', () => { observer.getCurrentResult().refetch, ).parameters.toEqualTypeOf<[options?: RefetchOptions]>() }) + }) + describe('status', () => { it('should type status as the query status union', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), @@ -456,7 +502,9 @@ describe('queryObserver', () => { observer.getCurrentResult().status, ).toEqualTypeOf() }) + }) + describe('fetchStatus', () => { it('should type fetchStatus as the fetch status union', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), From bb212a757aad28e3ecf78e7bf29599abed13da7b Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:09:57 +0900 Subject: [PATCH 09/43] test(query-core/queryObserver): group the branch type tests by their 'QueryObserverResult' member interface --- .../src/__tests__/queryObserver.test-d.tsx | 118 ++++++++++-------- 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 7ea59de975..f2fde5c60d 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -586,8 +586,8 @@ describe('queryObserver', () => { }) }) - describe('discriminants', () => { - it('should fix isPending to true on the pending branch', () => { + describe('QueryObserverPendingResult', () => { + it('should be extractable from the result union by its isPending literal', () => { type Pending = Extract< QueryObserverResult<{ value: string }>, { status: 'pending'; isLoading: boolean } @@ -596,54 +596,7 @@ describe('queryObserver', () => { expectTypeOf().toEqualTypeOf() }) - it('should fix isLoading to true on the loading branch', () => { - type Loading = Extract< - QueryObserverResult<{ value: string }>, - { isLoading: true } - > - - expectTypeOf().toEqualTypeOf() - }) - - it('should fix isSuccess to true on the success branch', () => { - type Success = Extract< - QueryObserverResult<{ value: string }>, - { status: 'success'; isPlaceholderData: false } - > - - expectTypeOf().toEqualTypeOf() - }) - - it('should fix isPlaceholderData to true on the placeholder branch', () => { - type Placeholder = Extract< - QueryObserverResult<{ value: string }>, - { isPlaceholderData: true } - > - - expectTypeOf().toEqualTypeOf() - }) - - it('should fix isLoadingError to true on the loading error branch', () => { - type LoadingError = Extract< - QueryObserverResult<{ value: string }>, - { isLoadingError: true } - > - - expectTypeOf().toEqualTypeOf() - }) - - it('should fix isRefetchError to true on the refetch error branch', () => { - type RefetchError = Extract< - QueryObserverResult<{ value: string }>, - { isRefetchError: true } - > - - expectTypeOf().toEqualTypeOf() - }) - }) - - describe('branches', () => { - it('should type every flag on the pending branch', () => { + it('should pin every flag along with its data and error types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -663,8 +616,19 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + }) + + describe('QueryObserverLoadingResult', () => { + it('should be extractable from the result union by its isLoading literal', () => { + type Loading = Extract< + QueryObserverResult<{ value: string }>, + { isLoading: true } + > + + expectTypeOf().toEqualTypeOf() + }) - it('should type every flag on the loading branch', () => { + it('should pin every flag along with its data and error types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -685,8 +649,19 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + }) + + describe('QueryObserverLoadingErrorResult', () => { + it('should be extractable from the result union by its isLoadingError literal', () => { + type LoadingError = Extract< + QueryObserverResult<{ value: string }>, + { isLoadingError: true } + > + + expectTypeOf().toEqualTypeOf() + }) - it('should type every flag on the loading error branch', () => { + it('should pin every flag along with its data and error types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -707,8 +682,19 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + }) + + describe('QueryObserverRefetchErrorResult', () => { + it('should be extractable from the result union by its isRefetchError literal', () => { + type RefetchError = Extract< + QueryObserverResult<{ value: string }>, + { isRefetchError: true } + > - it('should type every flag on the refetch error branch', () => { + expectTypeOf().toEqualTypeOf() + }) + + it('should pin every flag along with its data and error types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -729,8 +715,19 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + }) - it('should type every flag on the success branch', () => { + describe('QueryObserverSuccessResult', () => { + it('should be extractable from the result union by its isSuccess literal', () => { + type Success = Extract< + QueryObserverResult<{ value: string }>, + { status: 'success'; isPlaceholderData: false } + > + + expectTypeOf().toEqualTypeOf() + }) + + it('should pin every flag along with its data and error types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -751,8 +748,19 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + }) + + describe('QueryObserverPlaceholderResult', () => { + it('should be extractable from the result union by its isPlaceholderData literal', () => { + type Placeholder = Extract< + QueryObserverResult<{ value: string }>, + { isPlaceholderData: true } + > + + expectTypeOf().toEqualTypeOf() + }) - it('should type every flag on the placeholder branch', () => { + it('should pin every flag along with its data and error types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), From 58818fc2eea37dccb411f1862c8d94409d6cd13e Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:17:37 +0900 Subject: [PATCH 10/43] test(query-core/queryObserver): assert 'initialData' through the options it is given --- .../src/__tests__/queryObserver.test-d.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index f2fde5c60d..d27848a9b5 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -263,9 +263,19 @@ describe('queryObserver', () => { describe('initialData', () => { it('should type an initialData function from the data type', () => { - expectTypeOf< - InitialDataFunction<{ value: string }> - >().returns.toEqualTypeOf<{ value: string } | undefined>() + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + initialData: () => { + expectTypeOf< + QueryObserverOptions<{ value: string }>['initialData'] + >() + .extract>() + .returns.toEqualTypeOf<{ value: string } | undefined>() + + return { value: 'data' } + }, + }) }) }) From 6e567fe74b2839a456acbe4f084ba9459ba44b48 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:19:04 +0900 Subject: [PATCH 11/43] test(query-core/queryObserver): reuse the shared 'queryClient' in the 'placeholderData' tests --- packages/query-core/src/__tests__/queryObserver.test-d.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index d27848a9b5..96c8e376bf 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -297,7 +297,7 @@ describe('queryObserver', () => { it('should type the queryKey of the previousQuery it is given', () => { const testQueryKey = ['SomeQuery', 42, { foo: 'bar' }] as const - new QueryObserver(new QueryClient(), { + new QueryObserver(queryClient, { queryKey: testQueryKey, placeholderData: (_, previousQuery) => { if (previousQuery) { @@ -310,7 +310,7 @@ describe('queryObserver', () => { }) it('should type the error of the previousQuery it is given', () => { - new QueryObserver(new QueryClient(), { + new QueryObserver(queryClient, { queryKey: queryKey(), placeholderData: (_, previousQuery) => { if (previousQuery) { @@ -326,7 +326,7 @@ describe('queryObserver', () => { it('should type previousData as the query data', () => { const queryData = { foo: 'bar' } as const - new QueryObserver(new QueryClient(), { + new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => queryData, select: (data) => data.foo, From e003d2f788c8ead7a295c883d76573d35847e090 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:22:38 +0900 Subject: [PATCH 12/43] test(query-core/queryObserver): add type tests for its default type parameters and queryKey constraint --- .../src/__tests__/queryObserver.test-d.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 96c8e376bf..70fa4df94a 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -32,6 +32,26 @@ describe('queryObserver', () => { queryClient.clear() }) + describe('type parameters', () => { + it('should default to an unknown data type and the default error', () => { + expectTypeOf< + Awaited> + >().toEqualTypeOf>() + }) + + it('should only accept an array as its queryKey', () => { + const observer = new QueryObserver(queryClient, { + // @ts-expect-error a query key must be an array + queryKey: 'not-an-array', + queryFn: () => Promise.resolve('data'), + }) + + expectTypeOf(observer.getCurrentQuery().queryKey).toEqualTypeOf< + ReadonlyArray + >() + }) + }) + describe('QueryObserverOptions', () => { describe('queryFn', () => { it('should type the context given to the queryFn', () => { From 6224b3d91274ef352887acae802a9e6a873340f5 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:25:58 +0900 Subject: [PATCH 13/43] test(query-core/queryObserver): assert 'queryKey' is required and 'isInitialLoading' is a boolean --- .../src/__tests__/queryObserver.test-d.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 70fa4df94a..0daf7f2cb1 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -53,6 +53,19 @@ describe('queryObserver', () => { }) describe('QueryObserverOptions', () => { + describe('queryKey', () => { + it('should be required', () => { + // @ts-expect-error queryKey is required + new QueryObserver(queryClient, { + queryFn: () => Promise.resolve('data'), + }) + + expectTypeOf( + new QueryObserver(queryClient, { queryKey: queryKey() }), + ).toBeObject() + }) + }) + describe('queryFn', () => { it('should type the context given to the queryFn', () => { const key = ['a', 1] as const @@ -482,6 +495,7 @@ describe('queryObserver', () => { expectTypeOf(result.isEnabled).toEqualTypeOf() expectTypeOf(result.isFetched).toEqualTypeOf() expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() + expectTypeOf(result.isInitialLoading).toEqualTypeOf() }) }) From e02b8494fb33b54f7d6480f0d1a1cfc0a0196600 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:26:51 +0900 Subject: [PATCH 14/43] test(query-core/queryObserver): assert 'subscribe' returns an unsubscribe function --- .../query-core/src/__tests__/queryObserver.test-d.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 0daf7f2cb1..e435511c1f 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1084,6 +1084,17 @@ describe('queryObserver', () => { }) }) + describe('subscribe', () => { + it('should return an unsubscribe function', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + }) + + expectTypeOf(observer.subscribe(() => {})).toEqualTypeOf<() => void>() + }) + }) + describe('destroy', () => { it('should return void', () => { const observer = new QueryObserver(queryClient, { From 11c5cab8d26b07235062f7533eb8f48ce935d4e6 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:31:45 +0900 Subject: [PATCH 15/43] test(query-core/queryObserver): assert the return types of its callback options --- .../src/__tests__/queryObserver.test-d.tsx | 161 +++++++++++++++++- 1 file changed, 152 insertions(+), 9 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index e435511c1f..36a933228b 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -3,10 +3,9 @@ import { queryKey } from '@tanstack/query-test-utils' import { QueryClient, QueryObserver } from '..' import type { DefaultError, - FetchStatus, + InfiniteQueryObserverResult, InitialDataFunction, NetworkMode, - NotifyOnChangeProps, PlaceholderDataFunction, QueryMeta, QueryObserverOptions, @@ -95,6 +94,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a boolean from an enabled callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + // @ts-expect-error an enabled callback must return a boolean + enabled: () => 'yes', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('staleTime', () => { @@ -110,6 +122,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a StaleTime from a staleTime callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + // @ts-expect-error a staleTime callback must return a StaleTime + staleTime: () => 'soon', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('refetchInterval', () => { @@ -125,6 +150,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a number, false or undefined from a refetchInterval callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error a refetchInterval callback must return a number, false or undefined + refetchInterval: () => 'often', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('refetchOnWindowFocus', () => { @@ -140,6 +178,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a boolean or the always literal from a refetchOnWindowFocus callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error the callback must return a boolean or the always literal + refetchOnWindowFocus: () => 'sometimes', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('refetchOnReconnect', () => { @@ -155,6 +206,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a boolean or the always literal from a refetchOnReconnect callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error the callback must return a boolean or the always literal + refetchOnReconnect: () => 'sometimes', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('refetchOnMount', () => { @@ -170,6 +234,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a boolean or the always literal from a refetchOnMount callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error the callback must return a boolean or the always literal + refetchOnMount: () => 'sometimes', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('retryOnMount', () => { @@ -198,6 +275,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a boolean from a throwOnError callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error the callback must return a boolean + throwOnError: () => 'yes', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('retry', () => { @@ -210,6 +300,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a boolean from a retry callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error the callback must return a boolean + retry: () => 'maybe', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('retryDelay', () => { @@ -222,6 +325,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a number from a retryDelay callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error the callback must return a number + retryDelay: () => 'soon', + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('queryKeyHashFn', () => { @@ -237,6 +353,19 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept a string from a queryKeyHashFn', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve('data'), + // @ts-expect-error the callback must return a string + queryKeyHashFn: () => 42, + }) + + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('structuralSharing', () => { @@ -250,6 +379,12 @@ describe('queryObserver', () => { }, }) }) + + it('should only accept unknown from a structuralSharing callback', () => { + expectTypeOf< + Extract + >().returns.toEqualTypeOf() + }) }) describe('select', () => { @@ -446,7 +581,12 @@ describe('queryObserver', () => { it('should type notifyOnChangeProps as its named type', () => { expectTypeOf< QueryObserverOptions['notifyOnChangeProps'] - >().toEqualTypeOf() + >().toEqualTypeOf< + | Array + | 'all' + | undefined + | (() => Array | 'all' | undefined) + >() }) }) @@ -542,9 +682,12 @@ describe('queryObserver', () => { queryFn: () => Promise.resolve({ value: 'data' }), }) - expectTypeOf( - observer.getCurrentResult().status, - ).toEqualTypeOf() + expectTypeOf(observer.getCurrentResult().status).toEqualTypeOf< + 'pending' | 'error' | 'success' + >() + expectTypeOf().toEqualTypeOf< + 'pending' | 'error' | 'success' + >() }) }) @@ -555,9 +698,9 @@ describe('queryObserver', () => { queryFn: () => Promise.resolve({ value: 'data' }), }) - expectTypeOf( - observer.getCurrentResult().fetchStatus, - ).toEqualTypeOf() + expectTypeOf(observer.getCurrentResult().fetchStatus).toEqualTypeOf< + 'fetching' | 'paused' | 'idle' + >() }) }) From c43a35c4ea323bd97fb573acd1aacc3f8f036591 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:33:50 +0900 Subject: [PATCH 16/43] test(query-core/queryObserver): assert the remaining callback parameters of its options --- .../src/__tests__/queryObserver.test-d.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 36a933228b..ffb0908e7d 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -75,6 +75,7 @@ describe('queryObserver', () => { expectTypeOf(context.queryKey).toEqualTypeOf() expectTypeOf(context.signal).toEqualTypeOf() expectTypeOf(context.client).toEqualTypeOf() + expectTypeOf(context.meta).toEqualTypeOf() return Promise.resolve('data') }, }) @@ -269,8 +270,11 @@ describe('queryObserver', () => { new QueryObserver<{ value: string }, CustomError>(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), - throwOnError: (error) => { + throwOnError: (error, query) => { expectTypeOf(error).toEqualTypeOf() + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() return false }, }) @@ -294,7 +298,8 @@ describe('queryObserver', () => { it('should type the error given to a retry callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), - retry: (_failureCount, error) => { + retry: (failureCount, error) => { + expectTypeOf(failureCount).toEqualTypeOf() expectTypeOf(error).toEqualTypeOf() return false }, @@ -319,7 +324,8 @@ describe('queryObserver', () => { it('should type the error given to a retryDelay callback', () => { new QueryObserver(queryClient, { queryKey: queryKey(), - retryDelay: (_failureCount, error) => { + retryDelay: (failureCount, error) => { + expectTypeOf(failureCount).toEqualTypeOf() expectTypeOf(error).toEqualTypeOf() return 0 }, @@ -373,7 +379,8 @@ describe('queryObserver', () => { new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), - structuralSharing: (_oldData, newData) => { + structuralSharing: (oldData, newData) => { + expectTypeOf(oldData).toEqualTypeOf() expectTypeOf(newData).toEqualTypeOf() return newData }, From 15c07de89953b7b945ec2a72daedc6fd82bdc9ac Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:34:50 +0900 Subject: [PATCH 17/43] test(query-core/queryObserver): assert 'fetchFailureReason' carries the observed error type --- packages/query-core/src/__tests__/queryObserver.test-d.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index ffb0908e7d..84a163d251 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1139,6 +1139,9 @@ describe('queryObserver', () => { expectTypeOf( withCustomError.getCurrentQuery().state.error, ).toEqualTypeOf() + expectTypeOf( + withCustomError.getCurrentQuery().state.fetchFailureReason, + ).toEqualTypeOf() }) it('should keep the data type from before select', () => { From d42a3d23baafe720b2170d831a5bfe6d276e0f58 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:37:23 +0900 Subject: [PATCH 18/43] test(query-core/queryObserver): assert 'persister' through the options it is given --- .../src/__tests__/queryObserver.test-d.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 84a163d251..f1a4f7d625 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -7,10 +7,10 @@ import type { InitialDataFunction, NetworkMode, PlaceholderDataFunction, + Query, QueryMeta, QueryObserverOptions, QueryObserverResult, - QueryPersister, QueryStatus, RefetchOptions, } from '..' @@ -599,11 +599,23 @@ describe('queryObserver', () => { describe('persister', () => { it('should type persister from the queryFn data', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + persister: (persistedQueryFn, _context, query) => { + expectTypeOf(persistedQueryFn).returns.toEqualTypeOf< + { value: string } | Promise<{ value: string }> + >() + expectTypeOf(query).toEqualTypeOf() + + return { value: 'data' } + }, + }) + expectTypeOf< - QueryObserverOptions<{ value: string }>['persister'] - >().toEqualTypeOf< - | QueryPersister<{ value: string }, ReadonlyArray, never> - | undefined + NonNullable['persister']> + >().returns.toEqualTypeOf< + { value: string } | Promise<{ value: string }> >() }) }) From 92f5d34ca9068ede66a99d7baab713cd2c3be226 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:39:48 +0900 Subject: [PATCH 19/43] test(query-core/queryObserver): assert the parameter types of 'getOptimisticResult', 'fetchOptimistic' and 'trackResult' --- .../src/__tests__/queryObserver.test-d.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index f1a4f7d625..aabd75a021 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1049,6 +1049,10 @@ describe('queryObserver', () => { expectTypeOf( withCustomError.getOptimisticResult(customErrorOptions), ).toEqualTypeOf>() + + expectTypeOf(observer.getOptimisticResult) + .parameter(0) + .toEqualTypeOf() }) }) @@ -1099,6 +1103,14 @@ describe('queryObserver', () => { expectTypeOf( withCustomError.trackResult(withCustomError.getCurrentResult()), ).toEqualTypeOf>() + + expectTypeOf(observer.trackResult) + .parameter(0) + .toEqualTypeOf>() + + expectTypeOf(withCustomError.trackResult) + .parameter(0) + .toEqualTypeOf>() }) it('should type the tracked property in the onPropTracked callback', () => { @@ -1246,6 +1258,13 @@ describe('queryObserver', () => { expectTypeOf(withCustomError.fetchOptimistic).returns.toEqualTypeOf< Promise> >() + + expectTypeOf(observer.fetchOptimistic) + .parameter(0) + .toHaveProperty('select') + .toEqualTypeOf< + ((data: { value: string }) => { value: string }) | undefined + >() }) }) From dde9c10ebe5a13b88ef9df28ead39a94df098a43 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:42:08 +0900 Subject: [PATCH 20/43] test(query-core/queryObserver): assert the always-defaulted options of 'getOptimisticResult' --- .../src/__tests__/queryObserver.test-d.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index aabd75a021..db8a326942 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1054,6 +1054,31 @@ describe('queryObserver', () => { .parameter(0) .toEqualTypeOf() }) + + it('should require the options that are always defaulted', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + type Options = Parameters[0] + type OptionalKeys = { + [K in keyof Options]-?: {} extends Pick ? K : never + }[keyof Options] + + expectTypeOf< + 'throwOnError' extends OptionalKeys ? true : false + >().toEqualTypeOf() + expectTypeOf< + 'refetchOnReconnect' extends OptionalKeys ? true : false + >().toEqualTypeOf() + expectTypeOf< + 'queryHash' extends OptionalKeys ? true : false + >().toEqualTypeOf() + expectTypeOf().toEqualTypeOf< + ((data: { value: string }) => { value: string }) | undefined + >() + }) }) describe('getCurrentResult', () => { From dc288b1acbe6db2eabb4743a2e3bd7c03ad85d46 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:42:56 +0900 Subject: [PATCH 21/43] test(query-core/queryObserver): assert the shape of 'RefetchOptions' --- packages/query-core/src/__tests__/queryObserver.test-d.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index db8a326942..1d430fd81d 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1252,6 +1252,10 @@ describe('queryObserver', () => { expectTypeOf(observer.refetch).parameters.toEqualTypeOf< [options?: RefetchOptions] >() + expectTypeOf().toEqualTypeOf<{ + throwOnError?: boolean + cancelRefetch?: boolean + }>() }) }) From 401bf6d9d0d6c7aaaa4a16e659c7848103615edc Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:44:48 +0900 Subject: [PATCH 22/43] test(query-core/queryObserver): assert 'networkMode' and 'meta' by their actual shape --- packages/query-core/src/__tests__/queryObserver.test-d.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 1d430fd81d..ec4c3b441c 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -5,7 +5,6 @@ import type { DefaultError, InfiniteQueryObserverResult, InitialDataFunction, - NetworkMode, PlaceholderDataFunction, Query, QueryMeta, @@ -555,7 +554,7 @@ describe('queryObserver', () => { }) expectTypeOf().toEqualTypeOf< - NetworkMode | undefined + 'online' | 'always' | 'offlineFirst' | undefined >() }) }) @@ -579,7 +578,7 @@ describe('queryObserver', () => { describe('meta', () => { it('should type meta as its named type', () => { expectTypeOf().toEqualTypeOf< - QueryMeta | undefined + Record | undefined >() }) }) From 72ce8ada18309af9db11a9556837263f25236b86 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:48:08 +0900 Subject: [PATCH 23/43] test(query-core/queryObserver): assert 'select' keeps the query data type on the options that precede it --- .../src/__tests__/queryObserver.test-d.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index ec4c3b441c..f4c1a93d43 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -433,6 +433,38 @@ describe('queryObserver', () => { { myCount: number } | undefined >() }) + + it('should keep the query data type on the options that precede it', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ count: 1 }), + select: (data) => data.count, + placeholderData: (previousData) => { + expectTypeOf(previousData).toEqualTypeOf< + { count: number } | undefined + >() + return { count: 0 } + }, + enabled: (query) => { + expectTypeOf(query.state.data).toEqualTypeOf< + { count: number } | undefined + >() + return true + }, + }) + + expectTypeOf< + QueryObserverOptions< + { count: number }, + DefaultError, + number, + { count: number } + >['placeholderData'] + >() + .extract() + .parameter(0) + .toEqualTypeOf<{ count: number } | undefined>() + }) }) describe('initialData', () => { From 223583657f615c3594f009883e9aeb5baca649c8 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:49:09 +0900 Subject: [PATCH 24/43] test(query-core/queryObserver): assert the internal options it reads from the defaulted options --- packages/query-core/src/__tests__/queryObserver.test-d.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index f4c1a93d43..db36143e2d 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1109,6 +1109,10 @@ describe('queryObserver', () => { expectTypeOf().toEqualTypeOf< ((data: { value: string }) => { value: string }) | undefined >() + expectTypeOf().toEqualTypeOf< + 'optimistic' | 'isRestoring' | undefined + >() + expectTypeOf().toEqualTypeOf() }) }) From d03761261c2a681272b5f21b55e93295631d02fc Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:53:13 +0900 Subject: [PATCH 25/43] test(query-core/queryObserver): assert 'data' and 'error' on 'QueryObserverBaseResult' --- .../src/__tests__/queryObserver.test-d.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index db36143e2d..7b98de29bf 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -8,6 +8,7 @@ import type { PlaceholderDataFunction, Query, QueryMeta, + QueryObserverBaseResult, QueryObserverOptions, QueryObserverResult, QueryStatus, @@ -689,6 +690,22 @@ describe('queryObserver', () => { }) }) + describe('data', () => { + it('should type data from the observed data type on the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['data'] + >().toEqualTypeOf<{ value: string } | undefined>() + }) + }) + + describe('error', () => { + it('should type error from the observed error type on the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['error'] + >().toEqualTypeOf() + }) + }) + describe('failureReason', () => { it('should type failureReason from the error type', () => { const observer = new QueryObserver(queryClient, { From 3d76322ed6c9ba2aceda7e4e1c7ab2de39980b58 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:54:25 +0900 Subject: [PATCH 26/43] test(query-core/queryObserver): assert the discriminant flags and 'status' on 'QueryObserverBaseResult' --- .../src/__tests__/queryObserver.test-d.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 7b98de29bf..ed2061b084 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -706,6 +706,25 @@ describe('queryObserver', () => { }) }) + describe('discriminant flags', () => { + it('should type them as booleans on the base result', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) + + it('should type status as the query status union on the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['status'] + >().toEqualTypeOf<'pending' | 'error' | 'success'>() + }) + }) + describe('failureReason', () => { it('should type failureReason from the error type', () => { const observer = new QueryObserver(queryClient, { From ebc8d75cd7ef6496d00981ff3a10c9d1261e1b68 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:55:56 +0900 Subject: [PATCH 27/43] test(query-core/queryObserver): assert the remaining fields of the query state it exposes --- .../src/__tests__/queryObserver.test-d.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index ed2061b084..7106d9ff77 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1288,6 +1288,41 @@ describe('queryObserver', () => { readonly ['a', 1] >() }) + + it('should type the counters and timestamps of the query state as numbers', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const state = observer.getCurrentQuery().state + + expectTypeOf(state.dataUpdateCount).toEqualTypeOf() + expectTypeOf(state.dataUpdatedAt).toEqualTypeOf() + expectTypeOf(state.errorUpdateCount).toEqualTypeOf() + expectTypeOf(state.errorUpdatedAt).toEqualTypeOf() + expectTypeOf(state.fetchFailureCount).toEqualTypeOf() + }) + + it('should type the remaining fields of the query state', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const state = observer.getCurrentQuery().state + + expectTypeOf(state.isInvalidated).toEqualTypeOf() + expectTypeOf(state.fetchMeta).toEqualTypeOf<{ + fetchMore?: { direction: 'forward' | 'backward' } + } | null>() + expectTypeOf(state.status).toEqualTypeOf< + 'pending' | 'error' | 'success' + >() + expectTypeOf(state.fetchStatus).toEqualTypeOf< + 'fetching' | 'paused' | 'idle' + >() + }) }) describe('refetch', () => { From e922b737d0ae631bcc94e2123ffedd91d750ba87 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:56:49 +0900 Subject: [PATCH 28/43] test(query-core/queryObserver): assert 'queryHash' on the query it exposes --- packages/query-core/src/__tests__/queryObserver.test-d.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 7106d9ff77..d734c293a7 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1312,6 +1312,7 @@ describe('queryObserver', () => { const state = observer.getCurrentQuery().state + expectTypeOf(observer.getCurrentQuery().queryHash).toEqualTypeOf() expectTypeOf(state.isInvalidated).toEqualTypeOf() expectTypeOf(state.fetchMeta).toEqualTypeOf<{ fetchMore?: { direction: 'forward' | 'backward' } From fcc9a7a59c7c4c737b1779b48612a8628a343be0 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sat, 12 Sep 2026 23:59:57 +0900 Subject: [PATCH 29/43] test(query-core/queryObserver): assert the observed types on the 'options' field it exposes --- .../src/__tests__/queryObserver.test-d.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index d734c293a7..057631b599 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1081,6 +1081,25 @@ describe('queryObserver', () => { }) }) + describe('options', () => { + it('should expose the observed types through the options it holds', () => { + const observer = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf>() + .parameter(0) + .toEqualTypeOf() + expectTypeOf(observer.options.select).toEqualTypeOf< + ((data: { value: string }) => { value: string }) | undefined + >() + }) + }) + describe('getOptimisticResult', () => { it('should be typed from the options it is given', () => { const observer = new QueryObserver(queryClient, { From 452d1bd1cd3dc69ef5c1418a1851ce289d9700ba Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:01:43 +0900 Subject: [PATCH 30/43] test(query-core/queryObserver): assert 'data' and 'refetch' are always declared on the base result --- .../src/__tests__/queryObserver.test-d.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 057631b599..59b0d35411 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -696,6 +696,20 @@ describe('queryObserver', () => { QueryObserverBaseResult<{ value: string }, CustomError>['data'] >().toEqualTypeOf<{ value: string } | undefined>() }) + + it('should always declare data and refetch on the base result', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> + type OptionalKeys = { + [K in keyof Base]-?: {} extends Pick ? K : never + }[keyof Base] + + expectTypeOf< + 'data' extends OptionalKeys ? true : false + >().toEqualTypeOf() + expectTypeOf< + 'refetch' extends OptionalKeys ? true : false + >().toEqualTypeOf() + }) }) describe('error', () => { From d87ea2df08beb0b93094aa8d56f8bd538df5fdae Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:03:28 +0900 Subject: [PATCH 31/43] test(query-core/queryObserver): assert 'data' is always declared on the query state --- .../src/__tests__/queryObserver.test-d.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 59b0d35411..ef17e71710 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1337,6 +1337,26 @@ describe('queryObserver', () => { expectTypeOf(state.fetchFailureCount).toEqualTypeOf() }) + it('should always declare data on the query state', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + type State = (typeof observer)['getCurrentQuery'] extends () => { + state: infer TState + } + ? TState + : never + type OptionalKeys = { + [K in keyof State]-?: {} extends Pick ? K : never + }[keyof State] + + expectTypeOf< + 'data' extends OptionalKeys ? true : false + >().toEqualTypeOf() + }) + it('should type the remaining fields of the query state', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), From 79df994f1cd26061c73207aeadd2bf12c76157a0 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:08:19 +0900 Subject: [PATCH 32/43] test(query-core/queryObserver): assert the value branch of 'refetchInterval' --- packages/query-core/src/__tests__/queryObserver.test-d.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index ef17e71710..09840f8c5f 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -163,6 +163,9 @@ describe('queryObserver', () => { expectTypeOf< Extract >().returns.toEqualTypeOf() + expectTypeOf< + Exclude + >().toEqualTypeOf() }) }) From b1ea05bd42ed8603c28132bb5be888e725a082d7 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:10:54 +0900 Subject: [PATCH 33/43] test(query-core/queryObserver): assert the result properties stay writable --- .../src/__tests__/queryObserver.test-d.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 09840f8c5f..7a31b01c80 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -700,6 +700,43 @@ describe('queryObserver', () => { >().toEqualTypeOf<{ value: string } | undefined>() }) + it('should keep every property of the base result writable', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> + + expectTypeOf().toEqualTypeOf<{ + -readonly [K in keyof Base]: Base[K] + }>() + }) + + it('should keep every property of each result branch writable', () => { + type Writable = { -readonly [K in keyof T]: T[K] } + type Branch = Extract< + QueryObserverResult<{ value: string }, CustomError>, + TFilter + > + + expectTypeOf< + Branch<{ status: 'pending'; isLoading: boolean }> + >().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + }) + it('should always declare data and refetch on the base result', () => { type Base = QueryObserverBaseResult<{ value: string }, CustomError> type OptionalKeys = { From 939b4985a9827293089508368b7aca9ab541c8b0 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:11:37 +0900 Subject: [PATCH 34/43] test(query-core/queryObserver): assert the query and its state stay writable --- .../src/__tests__/queryObserver.test-d.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 7a31b01c80..71ae1dfc3b 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1417,6 +1417,24 @@ describe('queryObserver', () => { 'fetching' | 'paused' | 'idle' >() }) + + it('should keep the query state and its own fields writable', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + type CurrentQuery = ReturnType + type Fields = Pick + type State = CurrentQuery['state'] + + expectTypeOf().toEqualTypeOf<{ + -readonly [K in keyof Fields]: Fields[K] + }>() + expectTypeOf().toEqualTypeOf<{ + -readonly [K in keyof State]: State[K] + }>() + }) }) describe('refetch', () => { From d9d663c74d937acbcd043d58e40297a9e3297457 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:14:00 +0900 Subject: [PATCH 35/43] test(query-core/queryObserver): assert its options stay writable --- .../src/__tests__/queryObserver.test-d.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 71ae1dfc3b..8c18f143fe 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -52,6 +52,20 @@ describe('queryObserver', () => { }) describe('QueryObserverOptions', () => { + it('should keep every option writable', () => { + type Options = QueryObserverOptions< + { value: string }, + CustomError, + { value: string }, + { value: string }, + ReadonlyArray + > + + expectTypeOf().toEqualTypeOf<{ + -readonly [K in keyof Options]: Options[K] + }>() + }) + describe('queryKey', () => { it('should be required', () => { // @ts-expect-error queryKey is required From 26081a8b925f8468252724ba1cbfe4e4e1cfef2b Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:23:40 +0900 Subject: [PATCH 36/43] test(query-core/queryObserver): assert the type parameters it derives from the data type alone --- .../query-core/src/__tests__/queryObserver.test-d.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 8c18f143fe..e104635111 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -38,6 +38,17 @@ describe('queryObserver', () => { >().toEqualTypeOf>() }) + it('should derive the remaining type parameters from the data type alone', () => { + expectTypeOf< + QueryObserverOptions<{ value: string }>['select'] + >().toEqualTypeOf< + ((data: { value: string }) => { value: string }) | undefined + >() + expectTypeOf< + QueryObserverBaseResult<{ value: string }>['error'] + >().toEqualTypeOf() + }) + it('should only accept an array as its queryKey', () => { const observer = new QueryObserver(queryClient, { // @ts-expect-error a query key must be an array From 06e556a4baf8a86dc5382cda94fc2cd53644d6e8 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:25:50 +0900 Subject: [PATCH 37/43] test(query-core/queryObserver): move the type parameter tests under the option and member they belong to --- .../src/__tests__/queryObserver.test-d.tsx | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index e104635111..65be853ddd 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -31,37 +31,6 @@ describe('queryObserver', () => { queryClient.clear() }) - describe('type parameters', () => { - it('should default to an unknown data type and the default error', () => { - expectTypeOf< - Awaited> - >().toEqualTypeOf>() - }) - - it('should derive the remaining type parameters from the data type alone', () => { - expectTypeOf< - QueryObserverOptions<{ value: string }>['select'] - >().toEqualTypeOf< - ((data: { value: string }) => { value: string }) | undefined - >() - expectTypeOf< - QueryObserverBaseResult<{ value: string }>['error'] - >().toEqualTypeOf() - }) - - it('should only accept an array as its queryKey', () => { - const observer = new QueryObserver(queryClient, { - // @ts-expect-error a query key must be an array - queryKey: 'not-an-array', - queryFn: () => Promise.resolve('data'), - }) - - expectTypeOf(observer.getCurrentQuery().queryKey).toEqualTypeOf< - ReadonlyArray - >() - }) - }) - describe('QueryObserverOptions', () => { it('should keep every option writable', () => { type Options = QueryObserverOptions< @@ -77,7 +46,27 @@ describe('queryObserver', () => { }>() }) + it('should derive its remaining type parameters from the data type', () => { + expectTypeOf< + QueryObserverOptions<{ value: string }>['select'] + >().toEqualTypeOf< + ((data: { value: string }) => { value: string }) | undefined + >() + }) + describe('queryKey', () => { + it('should only accept an array', () => { + const observer = new QueryObserver(queryClient, { + // @ts-expect-error a query key must be an array + queryKey: 'not-an-array', + queryFn: () => Promise.resolve('data'), + }) + + expectTypeOf(observer.getCurrentQuery().queryKey).toEqualTypeOf< + ReadonlyArray + >() + }) + it('should be required', () => { // @ts-expect-error queryKey is required new QueryObserver(queryClient, { @@ -783,6 +772,12 @@ describe('queryObserver', () => { QueryObserverBaseResult<{ value: string }, CustomError>['error'] >().toEqualTypeOf() }) + + it('should default the error type of the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }>['error'] + >().toEqualTypeOf() + }) }) describe('discriminant flags', () => { @@ -1463,6 +1458,12 @@ describe('queryObserver', () => { }) describe('refetch', () => { + it('should resolve with an unknown data type when no type parameter is given', () => { + expectTypeOf< + Awaited> + >().toEqualTypeOf>() + }) + it('should resolve with the observed result type', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), From 7abb51f698a387d2e4ce7516968a7bf248af5de1 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:27:58 +0900 Subject: [PATCH 38/43] test(query-core/queryObserver): group the base result tests under 'QueryObserverBaseResult' --- .../src/__tests__/queryObserver.test-d.tsx | 319 +++++++++--------- 1 file changed, 163 insertions(+), 156 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 65be853ddd..006de536f9 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -671,196 +671,203 @@ describe('queryObserver', () => { }) describe('QueryObserverResult', () => { - describe('timestamps and counters', () => { - it('should type its timestamps and counters as numbers', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + describe('QueryObserverBaseResult', () => { + describe('timestamps and counters', () => { + it('should type its timestamps and counters as numbers', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + expectTypeOf(result.dataUpdatedAt).toEqualTypeOf() + expectTypeOf(result.errorUpdatedAt).toEqualTypeOf() + expectTypeOf(result.failureCount).toEqualTypeOf() + expectTypeOf(result.errorUpdateCount).toEqualTypeOf() }) - - const result = observer.getCurrentResult() - - expectTypeOf(result.dataUpdatedAt).toEqualTypeOf() - expectTypeOf(result.errorUpdatedAt).toEqualTypeOf() - expectTypeOf(result.failureCount).toEqualTypeOf() - expectTypeOf(result.errorUpdateCount).toEqualTypeOf() }) - }) - describe('state flags', () => { - it('should type its state flags as booleans', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) + describe('state flags', () => { + it('should type its state flags as booleans', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) - const result = observer.getCurrentResult() + const result = observer.getCurrentResult() - expectTypeOf(result.isFetching).toEqualTypeOf() - expectTypeOf(result.isRefetching).toEqualTypeOf() - expectTypeOf(result.isPaused).toEqualTypeOf() - expectTypeOf(result.isStale).toEqualTypeOf() - expectTypeOf(result.isEnabled).toEqualTypeOf() - expectTypeOf(result.isFetched).toEqualTypeOf() - expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() - expectTypeOf(result.isInitialLoading).toEqualTypeOf() + expectTypeOf(result.isFetching).toEqualTypeOf() + expectTypeOf(result.isRefetching).toEqualTypeOf() + expectTypeOf(result.isPaused).toEqualTypeOf() + expectTypeOf(result.isStale).toEqualTypeOf() + expectTypeOf(result.isEnabled).toEqualTypeOf() + expectTypeOf(result.isFetched).toEqualTypeOf() + expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() + expectTypeOf(result.isInitialLoading).toEqualTypeOf() + }) }) - }) - describe('data', () => { - it('should type data from the observed data type on the base result', () => { - expectTypeOf< - QueryObserverBaseResult<{ value: string }, CustomError>['data'] - >().toEqualTypeOf<{ value: string } | undefined>() - }) + describe('data', () => { + it('should type data from the observed data type on the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['data'] + >().toEqualTypeOf<{ value: string } | undefined>() + }) - it('should keep every property of the base result writable', () => { - type Base = QueryObserverBaseResult<{ value: string }, CustomError> + it('should keep every property of the base result writable', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> - expectTypeOf().toEqualTypeOf<{ - -readonly [K in keyof Base]: Base[K] - }>() - }) + expectTypeOf().toEqualTypeOf<{ + -readonly [K in keyof Base]: Base[K] + }>() + }) - it('should keep every property of each result branch writable', () => { - type Writable = { -readonly [K in keyof T]: T[K] } - type Branch = Extract< - QueryObserverResult<{ value: string }, CustomError>, - TFilter - > + it('should keep every property of each result branch writable', () => { + type Writable = { -readonly [K in keyof T]: T[K] } + type Branch = Extract< + QueryObserverResult<{ value: string }, CustomError>, + TFilter + > + + expectTypeOf< + Branch<{ status: 'pending'; isLoading: boolean }> + >().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + }) - expectTypeOf< - Branch<{ status: 'pending'; isLoading: boolean }> - >().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() + it('should always declare data and refetch on the base result', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> + type OptionalKeys = { + [K in keyof Base]-?: {} extends Pick ? K : never + }[keyof Base] + + expectTypeOf< + 'data' extends OptionalKeys ? true : false + >().toEqualTypeOf() + expectTypeOf< + 'refetch' extends OptionalKeys ? true : false + >().toEqualTypeOf() + }) }) - it('should always declare data and refetch on the base result', () => { - type Base = QueryObserverBaseResult<{ value: string }, CustomError> - type OptionalKeys = { - [K in keyof Base]-?: {} extends Pick ? K : never - }[keyof Base] - - expectTypeOf< - 'data' extends OptionalKeys ? true : false - >().toEqualTypeOf() - expectTypeOf< - 'refetch' extends OptionalKeys ? true : false - >().toEqualTypeOf() - }) - }) + describe('error', () => { + it('should type error from the observed error type on the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['error'] + >().toEqualTypeOf() + }) - describe('error', () => { - it('should type error from the observed error type on the base result', () => { - expectTypeOf< - QueryObserverBaseResult<{ value: string }, CustomError>['error'] - >().toEqualTypeOf() + it('should default the error type of the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }>['error'] + >().toEqualTypeOf() + }) }) - it('should default the error type of the base result', () => { - expectTypeOf< - QueryObserverBaseResult<{ value: string }>['error'] - >().toEqualTypeOf() - }) - }) + describe('discriminant flags', () => { + it('should type them as booleans on the base result', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> - describe('discriminant flags', () => { - it('should type them as booleans on the base result', () => { - type Base = QueryObserverBaseResult<{ value: string }, CustomError> + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() + it('should type status as the query status union on the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['status'] + >().toEqualTypeOf<'pending' | 'error' | 'success'>() + }) }) - it('should type status as the query status union on the base result', () => { - expectTypeOf< - QueryObserverBaseResult<{ value: string }, CustomError>['status'] - >().toEqualTypeOf<'pending' | 'error' | 'success'>() - }) - }) + describe('failureReason', () => { + it('should type failureReason from the error type', () => { + const observer = new QueryObserver( + queryClient, + { + queryKey: queryKey(), + }, + ) - describe('failureReason', () => { - it('should type failureReason from the error type', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), + expectTypeOf( + observer.getCurrentResult().failureReason, + ).toEqualTypeOf() }) - - expectTypeOf( - observer.getCurrentResult().failureReason, - ).toEqualTypeOf() }) - }) - describe('refetch', () => { - it('should type its refetch from the observed types', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + describe('refetch', () => { + it('should type its refetch from the observed types', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().refetch, + ).returns.toEqualTypeOf< + Promise> + >() }) - expectTypeOf(observer.getCurrentResult().refetch).returns.toEqualTypeOf< - Promise> - >() - }) + it('should only accept RefetchOptions in its refetch', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) - it('should only accept RefetchOptions in its refetch', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), + expectTypeOf( + observer.getCurrentResult().refetch, + ).parameters.toEqualTypeOf<[options?: RefetchOptions]>() }) - - expectTypeOf( - observer.getCurrentResult().refetch, - ).parameters.toEqualTypeOf<[options?: RefetchOptions]>() }) - }) - describe('status', () => { - it('should type status as the query status union', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) + describe('status', () => { + it('should type status as the query status union', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) - expectTypeOf(observer.getCurrentResult().status).toEqualTypeOf< - 'pending' | 'error' | 'success' - >() - expectTypeOf().toEqualTypeOf< - 'pending' | 'error' | 'success' - >() + expectTypeOf(observer.getCurrentResult().status).toEqualTypeOf< + 'pending' | 'error' | 'success' + >() + expectTypeOf().toEqualTypeOf< + 'pending' | 'error' | 'success' + >() + }) }) - }) - describe('fetchStatus', () => { - it('should type fetchStatus as the fetch status union', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) + describe('fetchStatus', () => { + it('should type fetchStatus as the fetch status union', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) - expectTypeOf(observer.getCurrentResult().fetchStatus).toEqualTypeOf< - 'fetching' | 'paused' | 'idle' - >() + expectTypeOf(observer.getCurrentResult().fetchStatus).toEqualTypeOf< + 'fetching' | 'paused' | 'idle' + >() + }) }) }) From 932fc4d615f3c7d3a9642ab92423d337c508819a Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:30:29 +0900 Subject: [PATCH 39/43] test(query-core/queryObserver): group the base result tests by the property they assert --- .../src/__tests__/queryObserver.test-d.tsx | 308 ++++++++++++++---- 1 file changed, 236 insertions(+), 72 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 006de536f9..14f55d0644 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -672,8 +672,59 @@ describe('queryObserver', () => { describe('QueryObserverResult', () => { describe('QueryObserverBaseResult', () => { - describe('timestamps and counters', () => { - it('should type its timestamps and counters as numbers', () => { + it('should keep every property writable', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> + + expectTypeOf().toEqualTypeOf<{ + -readonly [K in keyof Base]: Base[K] + }>() + }) + + it('should keep every property of each result branch writable', () => { + type Writable = { -readonly [K in keyof T]: T[K] } + type Branch = Extract< + QueryObserverResult<{ value: string }, CustomError>, + TFilter + > + + expectTypeOf< + Branch<{ status: 'pending'; isLoading: boolean }> + >().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + expectTypeOf>().toEqualTypeOf< + Writable> + >() + }) + + it('should always declare data and refetch', () => { + type Base = QueryObserverBaseResult<{ value: string }, CustomError> + type OptionalKeys = { + [K in keyof Base]-?: {} extends Pick ? K : never + }[keyof Base] + + expectTypeOf< + 'data' extends OptionalKeys ? true : false + >().toEqualTypeOf() + expectTypeOf< + 'refetch' extends OptionalKeys ? true : false + >().toEqualTypeOf() + }) + + describe('dataUpdatedAt', () => { + it('should be typed as a number', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -682,14 +733,50 @@ describe('queryObserver', () => { const result = observer.getCurrentResult() expectTypeOf(result.dataUpdatedAt).toEqualTypeOf() + }) + }) + + describe('errorUpdatedAt', () => { + it('should be typed as a number', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.errorUpdatedAt).toEqualTypeOf() + }) + }) + + describe('failureCount', () => { + it('should be typed as a number', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.failureCount).toEqualTypeOf() + }) + }) + + describe('errorUpdateCount', () => { + it('should be typed as a number', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.errorUpdateCount).toEqualTypeOf() }) }) - describe('state flags', () => { - it('should type its state flags as booleans', () => { + describe('isFetching', () => { + it('should be typed as a boolean', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -698,110 +785,181 @@ describe('queryObserver', () => { const result = observer.getCurrentResult() expectTypeOf(result.isFetching).toEqualTypeOf() + }) + }) + + describe('isRefetching', () => { + it('should be typed as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.isRefetching).toEqualTypeOf() + }) + }) + + describe('isPaused', () => { + it('should be typed as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.isPaused).toEqualTypeOf() + }) + }) + + describe('isStale', () => { + it('should be typed as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.isStale).toEqualTypeOf() + }) + }) + + describe('isEnabled', () => { + it('should be typed as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.isEnabled).toEqualTypeOf() + }) + }) + + describe('isFetched', () => { + it('should be typed as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.isFetched).toEqualTypeOf() + }) + }) + + describe('isFetchedAfterMount', () => { + it('should be typed as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.isFetchedAfterMount).toEqualTypeOf() + }) + }) + + describe('isInitialLoading', () => { + it('should be typed as a boolean', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + expectTypeOf(result.isInitialLoading).toEqualTypeOf() }) }) describe('data', () => { - it('should type data from the observed data type on the base result', () => { + it('should be typed from the observed data type', () => { expectTypeOf< QueryObserverBaseResult<{ value: string }, CustomError>['data'] >().toEqualTypeOf<{ value: string } | undefined>() }) + }) - it('should keep every property of the base result writable', () => { - type Base = QueryObserverBaseResult<{ value: string }, CustomError> - - expectTypeOf().toEqualTypeOf<{ - -readonly [K in keyof Base]: Base[K] - }>() + describe('error', () => { + it('should be typed from the observed error type', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['error'] + >().toEqualTypeOf() }) - it('should keep every property of each result branch writable', () => { - type Writable = { -readonly [K in keyof T]: T[K] } - type Branch = Extract< - QueryObserverResult<{ value: string }, CustomError>, - TFilter - > - + it('should default to the default error type', () => { expectTypeOf< - Branch<{ status: 'pending'; isLoading: boolean }> - >().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() - expectTypeOf>().toEqualTypeOf< - Writable> - >() + QueryObserverBaseResult<{ value: string }>['error'] + >().toEqualTypeOf() }) + }) - it('should always declare data and refetch on the base result', () => { - type Base = QueryObserverBaseResult<{ value: string }, CustomError> - type OptionalKeys = { - [K in keyof Base]-?: {} extends Pick ? K : never - }[keyof Base] - + describe('isPending', () => { + it('should be typed as a boolean', () => { expectTypeOf< - 'data' extends OptionalKeys ? true : false - >().toEqualTypeOf() - expectTypeOf< - 'refetch' extends OptionalKeys ? true : false - >().toEqualTypeOf() + QueryObserverBaseResult<{ value: string }, CustomError>['isPending'] + >().toEqualTypeOf() }) }) - describe('error', () => { - it('should type error from the observed error type on the base result', () => { + describe('isSuccess', () => { + it('should be typed as a boolean', () => { expectTypeOf< - QueryObserverBaseResult<{ value: string }, CustomError>['error'] - >().toEqualTypeOf() + QueryObserverBaseResult<{ value: string }, CustomError>['isSuccess'] + >().toEqualTypeOf() }) + }) - it('should default the error type of the base result', () => { + describe('isError', () => { + it('should be typed as a boolean', () => { expectTypeOf< - QueryObserverBaseResult<{ value: string }>['error'] - >().toEqualTypeOf() + QueryObserverBaseResult<{ value: string }, CustomError>['isError'] + >().toEqualTypeOf() }) }) - describe('discriminant flags', () => { - it('should type them as booleans on the base result', () => { - type Base = QueryObserverBaseResult<{ value: string }, CustomError> + describe('isLoadingError', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult< + { value: string }, + CustomError + >['isLoadingError'] + >().toEqualTypeOf() + }) + }) - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() + describe('isRefetchError', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult< + { value: string }, + CustomError + >['isRefetchError'] + >().toEqualTypeOf() }) + }) - it('should type status as the query status union on the base result', () => { + describe('isPlaceholderData', () => { + it('should be typed as a boolean', () => { expectTypeOf< - QueryObserverBaseResult<{ value: string }, CustomError>['status'] - >().toEqualTypeOf<'pending' | 'error' | 'success'>() + QueryObserverBaseResult< + { value: string }, + CustomError + >['isPlaceholderData'] + >().toEqualTypeOf() }) }) describe('failureReason', () => { - it('should type failureReason from the error type', () => { + it('should be typed from the observed error type', () => { const observer = new QueryObserver( queryClient, { @@ -816,7 +974,7 @@ describe('queryObserver', () => { }) describe('refetch', () => { - it('should type its refetch from the observed types', () => { + it('should be typed from the observed types', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -829,7 +987,7 @@ describe('queryObserver', () => { >() }) - it('should only accept RefetchOptions in its refetch', () => { + it('should only accept RefetchOptions', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -842,7 +1000,7 @@ describe('queryObserver', () => { }) describe('status', () => { - it('should type status as the query status union', () => { + it('should be typed as the query status union', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), @@ -855,10 +1013,16 @@ describe('queryObserver', () => { 'pending' | 'error' | 'success' >() }) + + it('should be typed as the query status union on the base result', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['status'] + >().toEqualTypeOf<'pending' | 'error' | 'success'>() + }) }) describe('fetchStatus', () => { - it('should type fetchStatus as the fetch status union', () => { + it('should be typed as the fetch status union', () => { const observer = new QueryObserver(queryClient, { queryKey: queryKey(), queryFn: () => Promise.resolve({ value: 'data' }), From 0ea5c58949ac2a2969227ca4c03ce434e5e9f62f Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:33:12 +0900 Subject: [PATCH 40/43] test(query-core/queryObserver): group the narrowing tests by the property they assert --- .../src/__tests__/queryObserver.test-d.tsx | 136 +++++++++--------- 1 file changed, 67 insertions(+), 69 deletions(-) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 14f55d0644..52ca2a5486 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -885,6 +885,47 @@ describe('queryObserver', () => { QueryObserverBaseResult<{ value: string }, CustomError>['data'] >().toEqualTypeOf<{ value: string } | undefined>() }) + + it('should stay possibly undefined on an isError check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isError) { + expectTypeOf(result.data).toEqualTypeOf< + { value: string } | undefined + >() + } + }) + + it('should be defined on a success status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'success') { + expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() + } + }) + + it('should be undefined on a pending status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'pending') { + expectTypeOf(result.data).toEqualTypeOf() + } + }) }) describe('error', () => { @@ -899,6 +940,32 @@ describe('queryObserver', () => { QueryObserverBaseResult<{ value: string }>['error'] >().toEqualTypeOf() }) + + it('should be the observed error type on an isError check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.isError) { + expectTypeOf(result.error).toEqualTypeOf() + } + }) + + it('should be the observed error type on an error status check', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const result = observer.getCurrentResult() + + if (result.status === 'error') { + expectTypeOf(result.error).toEqualTypeOf() + } + }) }) describe('isPending', () => { @@ -1035,75 +1102,6 @@ describe('queryObserver', () => { }) }) - describe('narrowing', () => { - it('should narrow error to the error type on an isError check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - if (result.isError) { - expectTypeOf(result.error).toEqualTypeOf() - } - }) - - it('should keep data possibly undefined on an isError check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - if (result.isError) { - expectTypeOf(result.data).toEqualTypeOf< - { value: string } | undefined - >() - } - }) - - it('should narrow data to be defined on a success status check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - if (result.status === 'success') { - expectTypeOf(result.data).toEqualTypeOf<{ value: string }>() - } - }) - - it('should narrow error to the error type on an error status check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - if (result.status === 'error') { - expectTypeOf(result.error).toEqualTypeOf() - } - }) - - it('should narrow data to undefined on a pending status check', () => { - const observer = new QueryObserver(queryClient, { - queryKey: queryKey(), - queryFn: () => Promise.resolve({ value: 'data' }), - }) - - const result = observer.getCurrentResult() - - if (result.status === 'pending') { - expectTypeOf(result.data).toEqualTypeOf() - } - }) - }) - describe('QueryObserverPendingResult', () => { it('should be extractable from the result union by its isPending literal', () => { type Pending = Extract< From afe1c218ae05795821b0dd8c926f7324cbbea23a Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 00:35:38 +0900 Subject: [PATCH 41/43] test(query-core/queryObserver): assert its 'persister' differs from the paged variant --- .../query-core/src/__tests__/queryObserver.test-d.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 52ca2a5486..c9b415c919 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -11,6 +11,7 @@ import type { QueryObserverBaseResult, QueryObserverOptions, QueryObserverResult, + QueryPersister, QueryStatus, RefetchOptions, } from '..' @@ -667,6 +668,14 @@ describe('queryObserver', () => { { value: string } | Promise<{ value: string }> >() }) + + it('should differ from the persister of a paged query', () => { + expectTypeOf< + QueryPersister<{ value: string }, ReadonlyArray, never> + >().not.toEqualTypeOf< + QueryPersister<{ value: string }, ReadonlyArray, number> + >() + }) }) }) From d5ab068ea194099963acfa2898d1928c59db50bd Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 09:50:07 +0900 Subject: [PATCH 42/43] test(query-core/queryObserver): assert each result branch declares exactly the base and narrowed keys --- .../src/__tests__/queryObserver.test-d.tsx | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index c9b415c919..1680de720e 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -3,14 +3,21 @@ import { queryKey } from '@tanstack/query-test-utils' import { QueryClient, QueryObserver } from '..' import type { DefaultError, + DefinedQueryObserverResult, InfiniteQueryObserverResult, InitialDataFunction, PlaceholderDataFunction, Query, QueryMeta, QueryObserverBaseResult, + QueryObserverLoadingErrorResult, + QueryObserverLoadingResult, QueryObserverOptions, + QueryObserverPendingResult, + QueryObserverPlaceholderResult, + QueryObserverRefetchErrorResult, QueryObserverResult, + QueryObserverSuccessResult, QueryPersister, QueryStatus, RefetchOptions, @@ -1141,6 +1148,12 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + + it('should declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) }) describe('QueryObserverLoadingResult', () => { @@ -1174,6 +1187,12 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + + it('should declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) }) describe('QueryObserverLoadingErrorResult', () => { @@ -1207,6 +1226,12 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + + it('should declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) }) describe('QueryObserverRefetchErrorResult', () => { @@ -1240,6 +1265,12 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + + it('should declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) }) describe('QueryObserverSuccessResult', () => { @@ -1273,6 +1304,12 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + + it('should declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) }) describe('QueryObserverPlaceholderResult', () => { @@ -1306,6 +1343,12 @@ describe('queryObserver', () => { expectTypeOf(result.isPlaceholderData).toEqualTypeOf() } }) + + it('should declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) }) }) From 89960ff5a66d6155c232a0f25de20c9af5faea3b Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 13 Sep 2026 09:50:53 +0900 Subject: [PATCH 43/43] test(query-core/queryObserver): assert the composition of 'DefinedQueryObserverResult' --- .../src/__tests__/queryObserver.test-d.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 1680de720e..cd4632afdf 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1350,6 +1350,38 @@ describe('queryObserver', () => { >() }) }) + + describe('DefinedQueryObserverResult', () => { + it('should only hold the branches that always have data', () => { + expectTypeOf< + DefinedQueryObserverResult<{ value: string }, CustomError> + >().toEqualTypeOf< + | QueryObserverRefetchErrorResult<{ value: string }, CustomError> + | QueryObserverSuccessResult<{ value: string }, CustomError> + >() + }) + + it('should always define data on every one of its branches', () => { + expectTypeOf< + DefinedQueryObserverResult<{ value: string }>['data'] + >().toEqualTypeOf<{ value: string }>() + }) + + it('should be a subset of the result union', () => { + expectTypeOf< + Exclude< + DefinedQueryObserverResult<{ value: string }>, + QueryObserverResult<{ value: string }> + > + >().toEqualTypeOf() + }) + + it('should default its data to unknown and its error to the default error', () => { + expectTypeOf().toEqualTypeOf< + DefinedQueryObserverResult + >() + }) + }) }) describe('setOptions', () => {