Skip to content

Commit

Permalink
fix(useQueries): add type tests for useQueries and fix a couple of ty…
Browse files Browse the repository at this point in the history
…pe bugs (#6471)

Creating new useQueries type tests in react-query and vue-query and fixing a couple of type bugs.
  • Loading branch information
charlotte-bone authored Dec 2, 2023
1 parent 23374bb commit b54936f
Show file tree
Hide file tree
Showing 4 changed files with 436 additions and 73 deletions.
148 changes: 148 additions & 0 deletions packages/react-query/src/__tests__/useQueries.types.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { describe, it } from 'vitest'
import { queryOptions, useQueries } from '..'
import { doNotExecute } from './utils'
import type { UseQueryOptions } from '..'
import type { Equal, Expect } from './utils'

describe('UseQueries config object overload', () => {
it('TData should always be defined when initialData is provided as an object', () => {
const query1 = {
queryKey: ['key1'],
queryFn: () => {
return {
wow: true,
}
},
initialData: {
wow: false,
},
}

const query2 = {
queryKey: ['key2'],
queryFn: () => 'Query Data',
initialData: 'initial data',
}

const query3 = {
queryKey: ['key2'],
queryFn: () => 'Query Data',
}

doNotExecute(() => {
const queryResults = useQueries({ queries: [query1, query2, query3] })

const query1Data = queryResults[0].data
const query2Data = queryResults[1].data
const query3Data = queryResults[2].data

const result1: Expect<Equal<{ wow: boolean }, typeof query1Data>> = true

const result2: Expect<Equal<string, typeof query2Data>> = true

const result3: Expect<Equal<string | undefined, typeof query3Data>> = true

return result1 && result2 && result3
})
})

it('TData should be defined when passed through queryOptions', () => {
doNotExecute(() => {
const options = queryOptions({
queryKey: ['key'],
queryFn: () => {
return {
wow: true,
}
},
initialData: {
wow: true,
},
})
const queryResults = useQueries({ queries: [options] })

const data = queryResults[0].data

const result: Expect<Equal<{ wow: boolean }, typeof data>> = true
return result
})
})

it('it should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQuery', () => {
doNotExecute(() => {
const query1 = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(1),
select: (data) => data > 1,
})

const query2 = {
queryKey: ['key'],
queryFn: () => Promise.resolve(1),
select: (data: number) => data > 1,
}

const queryResults = useQueries({ queries: [query1, query2] })
const query1Data = queryResults[0].data
const query2Data = queryResults[1].data

const result1: Expect<Equal<boolean | undefined, typeof query1Data>> =
true
const result2: Expect<Equal<boolean | undefined, typeof query2Data>> =
true
return result1 && result2
})
})

it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => {
doNotExecute(() => {
const queryResults = useQueries({
queries: [
{
queryKey: ['key'],
queryFn: () => {
return {
wow: true,
}
},
initialData: () => undefined as { wow: boolean } | undefined,
},
],
})

const data = queryResults[0].data

const result: Expect<Equal<{ wow: boolean } | undefined, typeof data>> =
true
return result
})
})

describe('custom hook', () => {
it('should allow custom hooks using UseQueryOptions', () => {
doNotExecute(() => {
type Data = string

const useCustomQueries = (
options?: Omit<UseQueryOptions<Data>, 'queryKey' | 'queryFn'>,
) => {
return useQueries({
queries: [
{
...options,
queryKey: ['todos-key'],
queryFn: () => Promise.resolve('data'),
},
],
})
}

const queryResults = useCustomQueries()
const data = queryResults[0].data

const result: Expect<Equal<Data | undefined, typeof data>> = true
return result
})
})
})
})
41 changes: 32 additions & 9 deletions packages/react-query/src/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
shouldSuspend,
willFetch,
} from './suspense'
import type { UseQueryOptions, UseQueryResult } from './types'
import type {
DefinedUseQueryResult,
UseQueryOptions,
UseQueryResult,
} from './types'
import type {
DefaultError,
QueriesObserverOptions,
Expand Down Expand Up @@ -95,36 +99,55 @@ type GetOptions<T> =
: // Fallback
UseQueryOptionsForUseQueries

// A defined initialData setting should return a DefinedUseQueryResult rather than UseQueryResult
type GetDefinedOrUndefinedQueryResult<T, TData, TError = unknown> = T extends {
initialData?: infer TInitialData
}
? unknown extends TInitialData
? UseQueryResult<TData, TError>
: TInitialData extends TData
? DefinedUseQueryResult<TData, TError>
: TInitialData extends () => infer TInitialDataResult
? unknown extends TInitialDataResult
? UseQueryResult<TData, TError>
: TInitialDataResult extends TData
? DefinedUseQueryResult<TData, TError>
: UseQueryResult<TData, TError>
: UseQueryResult<TData, TError>
: UseQueryResult<TData, TError>

type GetResults<T> =
// Part 1: responsible for mapping explicit type parameter to function result, if object
T extends { queryFnData: any; error?: infer TError; data: infer TData }
? UseQueryResult<TData, TError>
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: T extends { queryFnData: infer TQueryFnData; error?: infer TError }
? UseQueryResult<TQueryFnData, TError>
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
: T extends { data: infer TData; error?: infer TError }
? UseQueryResult<TData, TError>
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: // Part 2: responsible for mapping explicit type parameter to function result, if tuple
T extends [any, infer TError, infer TData]
? UseQueryResult<TData, TError>
? GetDefinedOrUndefinedQueryResult<T, TData, TError>
: T extends [infer TQueryFnData, infer TError]
? UseQueryResult<TQueryFnData, TError>
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData, TError>
: T extends [infer TQueryFnData]
? UseQueryResult<TQueryFnData>
? GetDefinedOrUndefinedQueryResult<T, TQueryFnData>
: // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided
T extends {
queryFn?: QueryFunction<infer TQueryFnData, any>
select?: (data: any) => infer TData
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseQueryResult<
? GetDefinedOrUndefinedQueryResult<
T,
unknown extends TData ? TQueryFnData : TData,
unknown extends TError ? DefaultError : TError
>
: T extends {
queryFn?: QueryFunction<infer TQueryFnData, any>
throwOnError?: ThrowOnError<any, infer TError, any, any>
}
? UseQueryResult<
? GetDefinedOrUndefinedQueryResult<
T,
TQueryFnData,
unknown extends TError ? DefaultError : TError
>
Expand Down
153 changes: 153 additions & 0 deletions packages/vue-query/src/__tests__/useQueries.types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { describe, it } from 'vitest'
import { reactive } from 'vue'
import { useQueries } from '..'
import { queryOptions } from '../queryOptions'
import { doNotExecute } from './test-utils'
import type { UseQueryOptions } from '../useQuery'
import type { Equal, Expect } from './test-utils'

describe('UseQueries config object overload', () => {
it('TData should always be defined when initialData is provided as an object', () => {
const query1 = {
queryKey: ['key1'],
queryFn: () => {
return {
wow: true,
}
},
initialData: {
wow: false,
},
}

const query2 = queryOptions({
queryKey: ['key2'],
queryFn: () => 'Query Data',
initialData: 'initial data',
})

const query3 = {
queryKey: ['key2'],
queryFn: () => 'Query Data',
}

doNotExecute(() => {
const { value: queriesState } = useQueries({
queries: [query1, query2, query3],
})

const query1Data = queriesState[0].data
const query2Data = queriesState[1].data
const query3Data = queriesState[2].data

const result1: Expect<Equal<{ wow: boolean }, typeof query1Data>> = true

const result2: Expect<Equal<string, typeof query2Data>> = true

const result3: Expect<Equal<string | undefined, typeof query3Data>> = true

return result1 && result2 && result3
})
})

it('TData should be defined when passed through queryOptions', () => {
doNotExecute(() => {
const options = queryOptions({
queryKey: ['key'],
queryFn: () => {
return {
wow: true,
}
},
initialData: {
wow: true,
},
})

const { value: queriesState } = useQueries({ queries: [options] })

const data = queriesState[0].data

const result: Expect<Equal<{ wow: boolean }, typeof data>> = true
return result
})
})

it('it should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQueries', () => {
doNotExecute(() => {
const query1 = queryOptions({
queryKey: ['key'],
queryFn: () => Promise.resolve(1),
select: (data) => data > 1,
})

const query2 = {
queryKey: ['key'],
queryFn: () => Promise.resolve(1),
select: (data: any) => data > 1,
}

const queriesState = reactive(useQueries({ queries: [query1, query2] }))
const query1Data = queriesState.value[0].data
const query2Data = queriesState.value[1].data

const result1: Expect<Equal<boolean | undefined, typeof query1Data>> =
true
const result2: Expect<Equal<boolean | undefined, typeof query2Data>> =
true
return result1 && result2
})
})

it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => {
doNotExecute(() => {
const { value: queriesState } = useQueries({
queries: [
{
queryKey: ['key'],
queryFn: () => {
return {
wow: true,
}
},
initialData: () => undefined as { wow: boolean } | undefined,
},
],
})

const data = queriesState[0].data

const result: Expect<Equal<{ wow: boolean } | undefined, typeof data>> =
true
return result
})
})

describe('custom hook', () => {
it('should allow custom hooks using UseQueryOptions', () => {
doNotExecute(() => {
type Data = string

const useCustomQueries = (
options?: Omit<UseQueryOptions<Data>, 'queryKey' | 'queryFn'>,
) => {
return useQueries({
queries: [
{
...options,
queryKey: ['todos-key'],
queryFn: () => Promise.resolve('data'),
},
],
})
}

const { value: queriesState } = useCustomQueries()
const data = queriesState[0].data

const result: Expect<Equal<Data | undefined, typeof data>> = true
return result
})
})
})
})
Loading

0 comments on commit b54936f

Please sign in to comment.