From 653e271be8921d06f9e4bf11d15b1fa41d758765 Mon Sep 17 00:00:00 2001 From: Jonghyeon Ko Date: Tue, 29 Apr 2025 23:33:54 +0900 Subject: [PATCH] test(react-query): use test-d file for useQuery --- .../src/__tests__/useQuery.test-d.tsx | 394 +++++++++++++----- .../src/__tests__/useQuery.test.tsx | 170 +------- 2 files changed, 285 insertions(+), 279 deletions(-) diff --git a/packages/react-query/src/__tests__/useQuery.test-d.tsx b/packages/react-query/src/__tests__/useQuery.test-d.tsx index 505344d6e8a..c7feaf3c86a 100644 --- a/packages/react-query/src/__tests__/useQuery.test-d.tsx +++ b/packages/react-query/src/__tests__/useQuery.test-d.tsx @@ -1,149 +1,321 @@ import { describe, expectTypeOf, it } from 'vitest' import { useQuery } from '../useQuery' import { queryOptions } from '../queryOptions' -import type { OmitKeyof } from '..' -import type { UseQueryOptions } from '../types' - -describe('initialData', () => { - describe('Config object overload', () => { - it('TData should always be defined when initialData is provided as an object', () => { - const { data } = useQuery({ - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: { wow: true }, - }) +import { queryKey } from './utils' +import type { OmitKeyof, QueryFunction, UseQueryOptions } from '..' + +describe('useQuery', () => { + const key = queryKey() + + // unspecified query function should default to unknown + const noQueryFn = useQuery({ queryKey: key }) + expectTypeOf(noQueryFn.data).toEqualTypeOf() + expectTypeOf(noQueryFn.error).toEqualTypeOf() + + // it should infer the result type from the query function + const fromQueryFn = useQuery({ queryKey: key, queryFn: () => 'test' }) + expectTypeOf(fromQueryFn.data).toEqualTypeOf() + expectTypeOf(fromQueryFn.error).toEqualTypeOf() + expectTypeOf(fromQueryFn.promise).toEqualTypeOf>() + + // it should be possible to specify the result type + const withResult = useQuery({ + queryKey: key, + queryFn: () => 'test', + }) + expectTypeOf(withResult.data).toEqualTypeOf() + expectTypeOf(withResult.error).toEqualTypeOf() + + // it should be possible to specify the error type + const withError = useQuery({ + queryKey: key, + queryFn: () => 'test', + }) + expectTypeOf(withError.data).toEqualTypeOf() + expectTypeOf(withError.error).toEqualTypeOf() + + // it should provide the result type in the configuration + useQuery({ + queryKey: [key], + queryFn: () => Promise.resolve(true), + }) + + // it should be possible to specify a union type as result type + const unionTypeSync = useQuery({ + queryKey: key, + queryFn: () => (Math.random() > 0.5 ? ('a' as const) : ('b' as const)), + }) + expectTypeOf(unionTypeSync.data).toEqualTypeOf<'a' | 'b' | undefined>() + const unionTypeAsync = useQuery<'a' | 'b'>({ + queryKey: key, + queryFn: () => Promise.resolve(Math.random() > 0.5 ? 'a' : 'b'), + }) + expectTypeOf(unionTypeAsync.data).toEqualTypeOf<'a' | 'b' | undefined>() + + // should error when the query function result does not match with the specified type + // @ts-expect-error + useQuery({ queryKey: key, queryFn: () => 'test' }) - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() + // it should infer the result type from a generic query function + function queryFn(): Promise { + return Promise.resolve({} as T) + } + + const fromGenericQueryFn = useQuery({ + queryKey: key, + queryFn: () => queryFn(), + }) + expectTypeOf(fromGenericQueryFn.data).toEqualTypeOf() + expectTypeOf(fromGenericQueryFn.error).toEqualTypeOf() + + const fromGenericOptionsQueryFn = useQuery({ + queryKey: key, + queryFn: () => queryFn(), + }) + expectTypeOf(fromGenericOptionsQueryFn.data).toEqualTypeOf< + string | undefined + >() + expectTypeOf(fromGenericOptionsQueryFn.error).toEqualTypeOf() + + type MyData = number + type MyQueryKey = readonly ['my-data', number] + + const getMyDataArrayKey: QueryFunction = ({ + queryKey: [, n], + }) => { + return Promise.resolve(n + 42) + } + + useQuery({ + queryKey: ['my-data', 100], + queryFn: getMyDataArrayKey, + }) + + const getMyDataStringKey: QueryFunction = (context) => { + expectTypeOf(context.queryKey).toEqualTypeOf<['1']>() + return Promise.resolve(Number(context.queryKey[0]) + 42) + } + + useQuery({ + queryKey: ['1'], + queryFn: getMyDataStringKey, + }) + + // it should handle query-functions that return Promise + useQuery({ + queryKey: key, + queryFn: () => fetch('return Promise').then((resp) => resp.json()), + }) + + // handles wrapped queries with custom fetcher passed as inline queryFn + const useWrappedQuery = < + TQueryKey extends [string, Record?], + TQueryFnData, + TError, + TData = TQueryFnData, + >( + qk: TQueryKey, + fetcher: ( + obj: TQueryKey[1], + token: string, + // return type must be wrapped with TQueryFnReturn + ) => Promise, + options?: OmitKeyof< + UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' + >, + ) => + useQuery({ + queryKey: qk, + queryFn: () => fetcher(qk[1], 'token'), + ...options, }) + const testQuery = useWrappedQuery([''], () => Promise.resolve('1')) + expectTypeOf(testQuery.data).toEqualTypeOf() - it('TData should be defined when passed through queryOptions', () => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => { - return { - wow: true, - } - }, - initialData: { - wow: true, - }, - }) - const { data } = useQuery(options) + // handles wrapped queries with custom fetcher passed directly to useQuery + const useWrappedFuncStyleQuery = < + TQueryKey extends [string, Record?], + TQueryFnData, + TError, + TData = TQueryFnData, + >( + qk: TQueryKey, + fetcher: () => Promise, + options?: OmitKeyof< + UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' + >, + ) => useQuery({ queryKey: qk, queryFn: fetcher, ...options }) + const testFuncStyle = useWrappedFuncStyleQuery([''], () => + Promise.resolve(true), + ) + expectTypeOf(testFuncStyle.data).toEqualTypeOf() - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() + it('should return the correct states for a successful query', async () => { + const state = useQuery({ + queryKey: key, + queryFn: () => Promise.resolve('test'), }) - it('should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQuery', () => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(1), - }) + if (state.isPending) { + expectTypeOf(state.data).toEqualTypeOf() + expectTypeOf(state.error).toEqualTypeOf() + return pending + } - const query = useQuery({ - ...options, - select: (data) => data > 1, - }) + if (state.isLoadingError) { + expectTypeOf(state.data).toEqualTypeOf() + expectTypeOf(state.error).toEqualTypeOf() + return {state.error.message} + } - expectTypeOf(query.data).toEqualTypeOf() - }) + expectTypeOf(state.data).toEqualTypeOf() + expectTypeOf(state.error).toEqualTypeOf() + return {state.data} + }) + + describe('initialData', () => { + describe('Config object overload', () => { + it('TData should always be defined when initialData is provided as an object', () => { + const { data } = useQuery({ + queryKey: ['key'], + queryFn: () => ({ wow: true }), + initialData: { wow: true }, + }) - it('TData should always be defined when initialData is provided as a function which ALWAYS returns the data', () => { - const { data } = useQuery({ - queryKey: ['key'], - queryFn: () => { - return { + expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() + }) + + it('TData should be defined when passed through queryOptions', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: { wow: true, - } - }, - initialData: () => ({ - wow: true, - }), + }, + }) + const { data } = useQuery(options) + + expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() }) - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() - }) + it('should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQuery', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(1), + }) - it('TData should have undefined in the union when initialData is NOT provided', () => { - const { data } = useQuery({ - queryKey: ['key'], - queryFn: () => { - return { + const query = useQuery({ + ...options, + select: (data) => data > 1, + }) + + expectTypeOf(query.data).toEqualTypeOf() + }) + + it('TData should always be defined when initialData is provided as a function which ALWAYS returns the data', () => { + const { data } = useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => ({ wow: true, - } - }, + }), + }) + + expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() }) - expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>() - }) + it('TData should have undefined in the union when initialData is NOT provided', () => { + const { data } = useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + }) - it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => { - const { data } = useQuery({ - queryKey: ['key'], - queryFn: () => { - return { - wow: true, - } - }, - initialData: () => undefined as { wow: boolean } | undefined, + expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>() }) - expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>() - }) + it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => { + const { data } = useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }) - it('TData should be narrowed after an isSuccess check when initialData is provided as a function which can return undefined', () => { - const { data, isSuccess } = useQuery({ - queryKey: ['key'], - queryFn: () => { - return { - wow: true, - } - }, - initialData: () => undefined as { wow: boolean } | undefined, + expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>() }) - if (isSuccess) { - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() - } - }) + it('TData should be narrowed after an isSuccess check when initialData is provided as a function which can return undefined', () => { + const { data, isSuccess } = useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }) - it('data should not have undefined when initialData is provided', () => { - const { data } = useQuery({ - queryKey: ['query-key'], - initialData: 42, + if (isSuccess) { + expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() + } }) - expectTypeOf(data).toEqualTypeOf() + it('data should not have undefined when initialData is provided', () => { + const { data } = useQuery({ + queryKey: ['query-key'], + initialData: 42, + }) + + expectTypeOf(data).toEqualTypeOf() + }) }) - }) - describe('custom hook', () => { - it('should allow custom hooks using UseQueryOptions', () => { - type Data = string + describe('custom hook', () => { + it('should allow custom hooks using UseQueryOptions', () => { + type Data = string - const useCustomQuery = ( - options?: OmitKeyof, 'queryKey' | 'queryFn'>, - ) => { - return useQuery({ - ...options, - queryKey: ['todos-key'], - queryFn: () => Promise.resolve('data'), - }) - } + const useCustomQuery = ( + options?: OmitKeyof, 'queryKey' | 'queryFn'>, + ) => { + return useQuery({ + ...options, + queryKey: ['todos-key'], + queryFn: () => Promise.resolve('data'), + }) + } - const { data } = useCustomQuery() + const { data } = useCustomQuery() - expectTypeOf(data).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf() + }) }) - }) - describe('structuralSharing', () => { - it('should restrict to same types', () => { - useQuery({ - queryKey: ['key'], - queryFn: () => 5, - structuralSharing: (_oldData, newData) => { - return newData - }, + describe('structuralSharing', () => { + it('should restrict to same types', () => { + useQuery({ + queryKey: ['key'], + queryFn: () => 5, + structuralSharing: (_oldData, newData) => { + return newData + }, + }) }) }) }) diff --git a/packages/react-query/src/__tests__/useQuery.test.tsx b/packages/react-query/src/__tests__/useQuery.test.tsx index dfea088fd04..a172b70f573 100644 --- a/packages/react-query/src/__tests__/useQuery.test.tsx +++ b/packages/react-query/src/__tests__/useQuery.test.tsx @@ -1,4 +1,4 @@ -import { describe, expect, expectTypeOf, it, test, vi } from 'vitest' +import { describe, expect, it, test, vi } from 'vitest' import { act, fireEvent, render, waitFor } from '@testing-library/react' import * as React from 'react' import { ErrorBoundary } from 'react-error-boundary' @@ -14,13 +14,7 @@ import { setActTimeout, sleep, } from './utils' -import type { - DefinedUseQueryResult, - OmitKeyof, - QueryFunction, - UseQueryOptions, - UseQueryResult, -} from '..' +import type { DefinedUseQueryResult, QueryFunction, UseQueryResult } from '..' import type { Mock } from 'vitest' describe('useQuery', () => { @@ -29,160 +23,6 @@ describe('useQuery', () => { queryCache, }) - it('should return the correct types', () => { - const key = queryKey() - - // @ts-expect-error - function Page() { - // unspecified query function should default to unknown - const noQueryFn = useQuery({ queryKey: key }) - expectTypeOf(noQueryFn.data).toEqualTypeOf() - expectTypeOf(noQueryFn.error).toEqualTypeOf() - - // it should infer the result type from the query function - const fromQueryFn = useQuery({ queryKey: key, queryFn: () => 'test' }) - expectTypeOf(fromQueryFn.data).toEqualTypeOf() - expectTypeOf(fromQueryFn.error).toEqualTypeOf() - expectTypeOf(fromQueryFn.promise).toEqualTypeOf>() - - // it should be possible to specify the result type - const withResult = useQuery({ - queryKey: key, - queryFn: () => 'test', - }) - expectTypeOf(withResult.data).toEqualTypeOf() - expectTypeOf(withResult.error).toEqualTypeOf() - - // it should be possible to specify the error type - const withError = useQuery({ - queryKey: key, - queryFn: () => 'test', - }) - expectTypeOf(withError.data).toEqualTypeOf() - expectTypeOf(withError.error).toEqualTypeOf() - - // it should provide the result type in the configuration - useQuery({ - queryKey: [key], - queryFn: () => Promise.resolve(true), - }) - - // it should be possible to specify a union type as result type - const unionTypeSync = useQuery({ - queryKey: key, - queryFn: () => (Math.random() > 0.5 ? ('a' as const) : ('b' as const)), - }) - expectTypeOf(unionTypeSync.data).toEqualTypeOf<'a' | 'b' | undefined>() - const unionTypeAsync = useQuery<'a' | 'b'>({ - queryKey: key, - queryFn: () => Promise.resolve(Math.random() > 0.5 ? 'a' : 'b'), - }) - expectTypeOf(unionTypeAsync.data).toEqualTypeOf<'a' | 'b' | undefined>() - - // should error when the query function result does not match with the specified type - // @ts-expect-error - useQuery({ queryKey: key, queryFn: () => 'test' }) - - // it should infer the result type from a generic query function - function queryFn(): Promise { - return Promise.resolve({} as T) - } - - const fromGenericQueryFn = useQuery({ - queryKey: key, - queryFn: () => queryFn(), - }) - expectTypeOf(fromGenericQueryFn.data).toEqualTypeOf() - expectTypeOf(fromGenericQueryFn.error).toEqualTypeOf() - - const fromGenericOptionsQueryFn = useQuery({ - queryKey: key, - queryFn: () => queryFn(), - }) - expectTypeOf(fromGenericOptionsQueryFn.data).toEqualTypeOf< - string | undefined - >() - expectTypeOf( - fromGenericOptionsQueryFn.error, - ).toEqualTypeOf() - - type MyData = number - type MyQueryKey = readonly ['my-data', number] - - const getMyDataArrayKey: QueryFunction = ({ - queryKey: [, n], - }) => { - return Promise.resolve(n + 42) - } - - useQuery({ - queryKey: ['my-data', 100], - queryFn: getMyDataArrayKey, - }) - - const getMyDataStringKey: QueryFunction = (context) => { - expectTypeOf(context.queryKey).toEqualTypeOf<['1']>() - return Promise.resolve(Number(context.queryKey[0]) + 42) - } - - useQuery({ - queryKey: ['1'], - queryFn: getMyDataStringKey, - }) - - // it should handle query-functions that return Promise - useQuery({ - queryKey: key, - queryFn: () => fetch('return Promise').then((resp) => resp.json()), - }) - - // handles wrapped queries with custom fetcher passed as inline queryFn - const useWrappedQuery = < - TQueryKey extends [string, Record?], - TQueryFnData, - TError, - TData = TQueryFnData, - >( - qk: TQueryKey, - fetcher: ( - obj: TQueryKey[1], - token: string, - // return type must be wrapped with TQueryFnReturn - ) => Promise, - options?: OmitKeyof< - UseQueryOptions, - 'queryKey' | 'queryFn' | 'initialData' - >, - ) => - useQuery({ - queryKey: qk, - queryFn: () => fetcher(qk[1], 'token'), - ...options, - }) - const testQuery = useWrappedQuery([''], () => Promise.resolve('1')) - expectTypeOf(testQuery.data).toEqualTypeOf() - - // handles wrapped queries with custom fetcher passed directly to useQuery - const useWrappedFuncStyleQuery = < - TQueryKey extends [string, Record?], - TQueryFnData, - TError, - TData = TQueryFnData, - >( - qk: TQueryKey, - fetcher: () => Promise, - options?: OmitKeyof< - UseQueryOptions, - 'queryKey' | 'queryFn' | 'initialData' - >, - ) => useQuery({ queryKey: qk, queryFn: fetcher, ...options }) - const testFuncStyle = useWrappedFuncStyleQuery([''], () => - Promise.resolve(true), - ) - expectTypeOf(testFuncStyle.data).toEqualTypeOf() - } - }) - // See https://github.com/tannerlinsley/react-query/issues/105 it('should allow to set default data value', async () => { const key = queryKey() @@ -226,19 +66,13 @@ describe('useQuery', () => { states.push(state) if (state.isPending) { - expectTypeOf(state.data).toEqualTypeOf() - expectTypeOf(state.error).toEqualTypeOf() return pending } if (state.isLoadingError) { - expectTypeOf(state.data).toEqualTypeOf() - expectTypeOf(state.error).toEqualTypeOf() return {state.error.message} } - expectTypeOf(state.data).toEqualTypeOf() - expectTypeOf(state.error).toEqualTypeOf() return {state.data} }