import { describe, expectTypeOf, it } from 'vitest' import { queryKey, sleep } from '@tanstack/query-test-utils' import { injectQuery, queryOptions } from '..' import type { Signal } from '@angular/core' import type { CreateQueryOptions, OmitKeyof, QueryFunction } from '..' describe('injectQuery', () => { it('should return the correct types', () => { const key = queryKey() // unspecified query function should default to unknown const noQueryFn = injectQuery(() => ({ queryKey: key, })) expectTypeOf(noQueryFn.data()).toEqualTypeOf() expectTypeOf(noQueryFn.error()).toEqualTypeOf() // it should infer the result type from the query function const fromQueryFn = injectQuery(() => ({ queryKey: key, queryFn: () => 'test', })) expectTypeOf(fromQueryFn.data()).toEqualTypeOf() expectTypeOf(fromQueryFn.error()).toEqualTypeOf() // it should be possible to specify the result type const withResult = injectQuery(() => ({ queryKey: key, queryFn: () => 'test', })) expectTypeOf(withResult.data()).toEqualTypeOf() expectTypeOf(withResult.error()).toEqualTypeOf() // it should be possible to specify the error type type CustomErrorType = { message: string } const withError = injectQuery(() => ({ queryKey: key, queryFn: () => 'test', })) expectTypeOf(withError.data()).toEqualTypeOf() expectTypeOf(withError.error()).toEqualTypeOf() // it should infer the result type from the configuration const withResultInfer = injectQuery(() => ({ queryKey: key, queryFn: () => true, })) expectTypeOf(withResultInfer.data()).toEqualTypeOf() expectTypeOf(withResultInfer.error()).toEqualTypeOf() // it should be possible to specify a union type as result type const unionTypeSync = injectQuery(() => ({ queryKey: key, queryFn: () => (Math.random() > 0.5 ? ('a' as const) : ('b' as const)), })) expectTypeOf(unionTypeSync.data()).toEqualTypeOf<'a' | 'b' | undefined>() const unionTypeAsync = injectQuery<'a' | 'b'>(() => ({ queryKey: key, queryFn: () => Promise.resolve(Math.random() > 0.5 ? 'a' : 'b'), })) expectTypeOf(unionTypeAsync.data()).toEqualTypeOf<'a' | 'b' | undefined>() // it should error when the query function result does not match with the specified type // @ts-expect-error injectQuery(() => ({ 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 = injectQuery(() => ({ queryKey: key, queryFn: () => queryFn(), })) expectTypeOf(fromGenericQueryFn.data()).toEqualTypeOf() expectTypeOf(fromGenericQueryFn.error()).toEqualTypeOf() type MyData = number type MyQueryKey = readonly ['my-data', number] const getMyDataArrayKey: QueryFunction = ({ queryKey: [, n], }) => { return n + 42 } const fromMyDataArrayKeyQueryFn = injectQuery(() => ({ queryKey: ['my-data', 100] as const, queryFn: getMyDataArrayKey, })) expectTypeOf(fromMyDataArrayKeyQueryFn.data()).toEqualTypeOf< number | undefined >() // it should handle query-functions that return Promise const fromPromiseAnyQueryFn = injectQuery(() => ({ queryKey: key, queryFn: () => fetch('return Promise').then((resp) => resp.json()), })) expectTypeOf(fromPromiseAnyQueryFn.data()).toEqualTypeOf() const getMyDataStringKey: QueryFunction = (context) => { expectTypeOf(context.queryKey).toEqualTypeOf<['1']>() return Number(context.queryKey[0]) + 42 } const fromGetMyDataStringKeyQueryFn = injectQuery(() => ({ queryKey: ['1'] as ['1'], queryFn: getMyDataStringKey, })) expectTypeOf(fromGetMyDataStringKeyQueryFn.data()).toEqualTypeOf< number | undefined >() // handles wrapped queries with custom fetcher passed as inline queryFn const createWrappedQuery = < 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< CreateQueryOptions, 'queryKey' | 'queryFn' | 'initialData', 'safely' >, ) => injectQuery(() => ({ queryKey: qk, queryFn: () => fetcher(qk[1], 'token'), ...options, })) const fromWrappedQuery = createWrappedQuery([''], () => Promise.resolve('1'), ) expectTypeOf(fromWrappedQuery.data()).toEqualTypeOf() // handles wrapped queries with custom fetcher passed directly to createQuery const createWrappedFuncStyleQuery = < TQueryKey extends [string, Record?], TQueryFnData, TError, TData = TQueryFnData, >( qk: TQueryKey, fetcher: () => Promise, options?: OmitKeyof< CreateQueryOptions, 'queryKey' | 'queryFn' | 'initialData', 'safely' >, ) => injectQuery(() => ({ queryKey: qk, queryFn: fetcher, ...options })) const fromWrappedFuncStyleQuery = createWrappedFuncStyleQuery([''], () => Promise.resolve(true), ) expectTypeOf(fromWrappedFuncStyleQuery.data()).toEqualTypeOf< boolean | undefined >() }) describe('initialData', () => { describe('Config object overload', () => { it('TData should always be defined when initialData is provided as an object', () => { const key = queryKey() const { data } = injectQuery(() => ({ queryKey: key, queryFn: () => ({ wow: true }), initialData: { wow: true }, })) expectTypeOf(data).toEqualTypeOf>() }) it('TData should be defined when passed through queryOptions', () => { const key = queryKey() const options = () => queryOptions({ queryKey: key, queryFn: () => { return { wow: true, } }, initialData: { wow: true, }, }) const { data } = injectQuery(options) expectTypeOf(data).toEqualTypeOf>() }) it('should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQuery', () => { const key = queryKey() const options = queryOptions({ queryKey: key, queryFn: () => Promise.resolve(1), }) const query = injectQuery(() => ({ ...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 key = queryKey() const { data } = injectQuery(() => ({ queryKey: key, queryFn: () => { return { wow: true, } }, initialData: () => ({ wow: true, }), })) expectTypeOf(data).toEqualTypeOf>() }) it('TData should have undefined in the union when initialData is NOT provided', () => { const key = queryKey() const { data } = injectQuery(() => ({ queryKey: key, queryFn: () => { return { wow: true, } }, })) expectTypeOf(data).toEqualTypeOf>() }) it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => { const key = queryKey() const { data } = injectQuery(() => ({ queryKey: key, queryFn: () => { return { wow: true, } }, initialData: () => undefined as { wow: boolean } | undefined, })) expectTypeOf(data).toEqualTypeOf>() }) it('TData should be narrowed after an isSuccess check when initialData is provided as a function which can return undefined', () => { const key = queryKey() const query = injectQuery(() => ({ queryKey: key, queryFn: () => { return { wow: true, } }, initialData: () => undefined as { wow: boolean } | undefined, })) if (query.isSuccess()) { expectTypeOf(query.data).toEqualTypeOf>() } }) }) describe('structuralSharing', () => { it('should be able to use structuralSharing with unknown types', () => { const key = queryKey() // https://github.com/TanStack/query/issues/6525#issuecomment-1938411343 injectQuery(() => ({ queryKey: key, queryFn: () => 5, structuralSharing: (oldData, newData) => { expectTypeOf(oldData).toBeUnknown() expectTypeOf(newData).toBeUnknown() return newData }, })) }) }) }) describe('Discriminated union return type', () => { it('data should be possibly undefined by default', () => { const key = queryKey() const query = injectQuery(() => ({ queryKey: key, queryFn: () => sleep(0).then(() => 'Some data'), })) expectTypeOf(query.data).toEqualTypeOf>() }) it('data should be defined when query is success', () => { const key = queryKey() const query = injectQuery(() => ({ queryKey: key, queryFn: () => sleep(0).then(() => 'Some data'), })) if (query.isSuccess()) { expectTypeOf(query.data).toEqualTypeOf>() } }) it('error should be null when query is success', () => { const key = queryKey() const query = injectQuery(() => ({ queryKey: key, queryFn: () => sleep(0).then(() => 'Some data'), })) if (query.isSuccess()) { expectTypeOf(query.error).toEqualTypeOf>() } }) it('data should be undefined when query is pending', () => { const key = queryKey() const query = injectQuery(() => ({ queryKey: key, queryFn: () => sleep(0).then(() => 'Some data'), })) if (query.isPending()) { expectTypeOf(query.data).toEqualTypeOf>() } }) it('error should be defined when query is error', () => { const key = queryKey() const query = injectQuery(() => ({ queryKey: key, queryFn: () => sleep(0).then(() => 'Some data'), })) if (query.isError()) { expectTypeOf(query.error).toEqualTypeOf>() } }) }) })