diff --git a/packages/query-core/src/__tests__/queryObserver.test-d.tsx b/packages/query-core/src/__tests__/queryObserver.test-d.tsx index 90db06e902..cd4632afdf 100644 --- a/packages/query-core/src/__tests__/queryObserver.test-d.tsx +++ b/packages/query-core/src/__tests__/queryObserver.test-d.tsx @@ -1,7 +1,31 @@ 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, + DefinedQueryObserverResult, + InfiniteQueryObserverResult, + InitialDataFunction, + PlaceholderDataFunction, + Query, + QueryMeta, + QueryObserverBaseResult, + QueryObserverLoadingErrorResult, + QueryObserverLoadingResult, + QueryObserverOptions, + QueryObserverPendingResult, + QueryObserverPlaceholderResult, + QueryObserverRefetchErrorResult, + QueryObserverResult, + QueryObserverSuccessResult, + QueryPersister, + QueryStatus, + RefetchOptions, +} from '..' + +class CustomError extends Error { + name = 'CustomError' as const +} describe('queryObserver', () => { let queryClient: QueryClient @@ -15,169 +39,1828 @@ 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('select', () => { - 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 }), - }) - - observer.subscribe((result) => { - expectTypeOf(result).toEqualTypeOf< - QueryObserverResult<{ myCount: number }> - >() - }) - }) - - 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 }), - }) - - const observerResult = await observer.refetch() - - expectTypeOf(observerResult.data).toEqualTypeOf< - { myCount: number } | undefined - >() - }) - }) - - describe('placeholderData', () => { - 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 + 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] + }>() + }) + + 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, { + 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 + + new QueryObserver(queryClient, { + queryKey: key, + queryFn: (context) => { + expectTypeOf(context.queryKey).toEqualTypeOf() + expectTypeOf(context.signal).toEqualTypeOf() + expectTypeOf(context.client).toEqualTypeOf() + expectTypeOf(context.meta).toEqualTypeOf() + return Promise.resolve('data') + }, + }) + }) + }) + + describe('enabled', () => { + 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 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() }) }) - it('previousQuery should have typed error', () => { - class CustomError extends Error { - name = 'CustomError' as const - } + describe('staleTime', () => { + 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 + }, + }) + }) - new QueryObserver(new QueryClient(), { - queryKey: queryKey(), - placeholderData: (_, previousQuery) => { - if (previousQuery) { - expectTypeOf( - previousQuery.state.error, - ).toEqualTypeOf() - } - return undefined - }, + 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', () => { + 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 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() + expectTypeOf< + Exclude + >().toEqualTypeOf() + }) + }) + + describe('refetchOnWindowFocus', () => { + 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 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() }) }) - it('previousData should have the same type as query data', () => { - const queryData = { foo: 'bar' } as const + describe('refetchOnReconnect', () => { + 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 + }, + }) + }) - new QueryObserver(new QueryClient(), { - queryKey: queryKey(), - queryFn: () => queryData, - select: (data) => data.foo, - placeholderData: (previousData) => { - expectTypeOf(previousData).toEqualTypeOf< - typeof queryData | undefined + 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', () => { + 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 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', () => { + 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 + }, + }) + }) + }) + + describe('throwOnError', () => { + 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, query) => { + expectTypeOf(error).toEqualTypeOf() + expectTypeOf(query.state.data).toEqualTypeOf< + { value: string } | undefined + >() + return false + }, + }) + }) + + 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', () => { + it('should type the error given to a retry callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + retry: (failureCount, error) => { + expectTypeOf(failureCount).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + return false + }, + }) + }) + + 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', () => { + it('should type the error given to a retryDelay callback', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + retryDelay: (failureCount, error) => { + expectTypeOf(failureCount).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + return 0 + }, + }) + }) + + 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', () => { + 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 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', () => { + it('should type a structuralSharing callback as unknown', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + structuralSharing: (oldData, newData) => { + expectTypeOf(oldData).toEqualTypeOf() + expectTypeOf(newData).toEqualTypeOf() + return newData + }, + }) + }) + + it('should only accept unknown from a structuralSharing callback', () => { + expectTypeOf< + Extract + >().returns.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(), + queryFn: () => ({ count: 1 }), + select: (data) => ({ myCount: data.count }), + }) + + observer.subscribe((result) => { + expectTypeOf(result).toEqualTypeOf< + QueryObserverResult<{ myCount: number }> >() - return undefined - }, + }) + }) + + 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 }), + }) + + const observerResult = await observer.refetch() + + expectTypeOf(observerResult.data).toEqualTypeOf< + { 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', () => { + it('should type an initialData function from the data type', () => { + 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' } + }, + }) + }) + }) + + describe('initialDataUpdatedAt', () => { + it('should accept a function for initialDataUpdatedAt', () => { + expectTypeOf< + QueryObserverOptions['initialDataUpdatedAt'] + >().toEqualTypeOf number | undefined) | undefined>() + }) + }) + + describe('placeholderData', () => { + it('should return the query data type or undefined', () => { + expectTypeOf< + PlaceholderDataFunction<{ value: string }> + >().returns.toEqualTypeOf<{ value: string } | undefined>() + }) + + it('should type the queryKey of the previousQuery it is given', () => { + const testQueryKey = ['SomeQuery', 42, { foo: 'bar' }] as const + + new QueryObserver(queryClient, { + queryKey: testQueryKey, + placeholderData: (_, previousQuery) => { + if (previousQuery) { + expectTypeOf(previousQuery.queryKey).toEqualTypeOf< + typeof testQueryKey + >() + } + }, + }) + }) + + it('should type the error of the previousQuery it is given', () => { + new QueryObserver(queryClient, { + queryKey: queryKey(), + placeholderData: (_, previousQuery) => { + if (previousQuery) { + expectTypeOf( + previousQuery.state.error, + ).toEqualTypeOf() + } + return undefined + }, + }) + }) + + it('should type previousData as the query data', () => { + const queryData = { foo: 'bar' } as const + + new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => queryData, + select: (data) => data.foo, + placeholderData: (previousData) => { + expectTypeOf(previousData).toEqualTypeOf< + typeof queryData | undefined + >() + return undefined + }, + }) + }) + }) + + describe('gcTime', () => { + 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 + >() + }) + }) + + describe('queryHash', () => { + 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 + >() + }) + }) + + describe('networkMode', () => { + 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< + 'online' | 'always' | 'offlineFirst' | 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< + Record | undefined + >() + }) + }) + + describe('notifyOnChangeProps', () => { + it('should type notifyOnChangeProps as its named type', () => { + expectTypeOf< + QueryObserverOptions['notifyOnChangeProps'] + >().toEqualTypeOf< + | Array + | 'all' + | undefined + | (() => Array | 'all' | undefined) + >() + }) + }) + + 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< + NonNullable['persister']> + >().returns.toEqualTypeOf< + { 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> + >() + }) + }) + }) + + describe('QueryObserverResult', () => { + describe('QueryObserverBaseResult', () => { + 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' }), + }) + + 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('isFetching', () => { + 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.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 be typed from the observed data type', () => { + expectTypeOf< + 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', () => { + it('should be typed from the observed error type', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['error'] + >().toEqualTypeOf() + }) + + it('should default to the default error type', () => { + expectTypeOf< + 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', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['isPending'] + >().toEqualTypeOf() + }) + }) + + describe('isSuccess', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['isSuccess'] + >().toEqualTypeOf() + }) + }) + + describe('isError', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult<{ value: string }, CustomError>['isError'] + >().toEqualTypeOf() + }) + }) + + describe('isLoadingError', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult< + { value: string }, + CustomError + >['isLoadingError'] + >().toEqualTypeOf() + }) + }) + + describe('isRefetchError', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult< + { value: string }, + CustomError + >['isRefetchError'] + >().toEqualTypeOf() + }) + }) + + describe('isPlaceholderData', () => { + it('should be typed as a boolean', () => { + expectTypeOf< + QueryObserverBaseResult< + { value: string }, + CustomError + >['isPlaceholderData'] + >().toEqualTypeOf() + }) + }) + + describe('failureReason', () => { + it('should be typed from the observed error type', () => { + const observer = new QueryObserver( + queryClient, + { + queryKey: queryKey(), + }, + ) + + expectTypeOf( + observer.getCurrentResult().failureReason, + ).toEqualTypeOf() + }) + }) + + describe('refetch', () => { + it('should be typed 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', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.getCurrentResult().refetch, + ).parameters.toEqualTypeOf<[options?: RefetchOptions]>() + }) + }) + + describe('status', () => { + it('should be typed 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' + >() + }) + + 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 be typed 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' + >() + }) + }) + }) + + describe('QueryObserverPendingResult', () => { + it('should be extractable from the result union by its isPending literal', () => { + type Pending = Extract< + QueryObserverResult<{ value: string }>, + { status: 'pending'; isLoading: boolean } + > + + 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' }), + }) + + 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 declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) + }) + + 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 pin every flag along with its data and error types', () => { + 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 declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) + }) + + 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 pin every flag along with its data and error types', () => { + 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 declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) + }) + + describe('QueryObserverRefetchErrorResult', () => { + it('should be extractable from the result union by its isRefetchError literal', () => { + type RefetchError = Extract< + QueryObserverResult<{ value: string }>, + { isRefetchError: true } + > + + 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' }), + }) + + 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 declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) + }) + + 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' }), + }) + + 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 declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) + }) + + 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 pin every flag along with its data and error types', () => { + 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() + } + }) + + it('should declare exactly the base and narrowed keys', () => { + expectTypeOf().toEqualTypeOf< + keyof QueryObserverBaseResult + >() + }) + }) + + 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', () => { + 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('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, { + 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>() + + expectTypeOf(observer.getOptimisticResult) + .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 + >() + expectTypeOf().toEqualTypeOf< + 'optimistic' | 'isRestoring' | undefined + >() + expectTypeOf().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, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf( + observer.trackResult(observer.getCurrentResult()), + ).toEqualTypeOf>() + + const withCustomError = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + 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', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + observer.trackResult(observer.getCurrentResult(), (key) => { + expectTypeOf(key).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('getCurrentQuery', () => { + it('should carry the observed types 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 + >() + expectTypeOf( + observer.getCurrentQuery().state.error, + ).toEqualTypeOf() + + const withCustomError = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf( + withCustomError.getCurrentQuery().state.error, + ).toEqualTypeOf() + expectTypeOf( + withCustomError.getCurrentQuery().state.fetchFailureReason, + ).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] + >() + }) + + 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 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(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + const state = observer.getCurrentQuery().state + + expectTypeOf(observer.getCurrentQuery().queryHash).toEqualTypeOf() + 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' + >() + }) + + 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', () => { + 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(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.refetch()).toEqualTypeOf< + Promise> + >() + + const withCustomError = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf(withCustomError.refetch()).toEqualTypeOf< + Promise> + >() + }) + + it('should only accept RefetchOptions', () => { + const observer = new QueryObserver(queryClient, { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }) + + expectTypeOf(observer.refetch).parameters.toEqualTypeOf< + [options?: RefetchOptions] + >() + expectTypeOf().toEqualTypeOf<{ + throwOnError?: boolean + cancelRefetch?: boolean + }>() + }) + }) + + 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, + }) + + const withCustomError = new QueryObserver<{ value: string }, CustomError>( + queryClient, + { + queryKey: queryKey(), + queryFn: () => Promise.resolve({ value: 'data' }), + }, + ) + + expectTypeOf(withCustomError.fetchOptimistic).returns.toEqualTypeOf< + Promise> + >() + + expectTypeOf(observer.fetchOptimistic) + .parameter(0) + .toHaveProperty('select') + .toEqualTypeOf< + ((data: { value: string }) => { value: string }) | undefined + >() + }) + }) + + 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, { + 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() }) }) })