diff --git a/packages/angular-query-experimental/src/__tests__/inject-query.test-d.ts b/packages/angular-query-experimental/src/__tests__/inject-query.test-d.ts index c0864c0c99..5d0f35fbb9 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-query.test-d.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-query.test-d.ts @@ -2,8 +2,183 @@ 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() + + // todo use query options? + const fromGenericOptionsQueryFn = injectQuery(() => ({ + 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 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', () => { diff --git a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts index a8a241b3c5..c36aa0d1ff 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts @@ -3,7 +3,6 @@ import { Component, Injector, computed, - effect, input, provideZonelessChangeDetection, signal, @@ -14,15 +13,7 @@ import { HttpTestingController, provideHttpClientTesting, } from '@angular/common/http/testing' -import { - afterEach, - beforeEach, - describe, - expect, - expectTypeOf, - it, - vi, -} from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { render } from '@testing-library/angular' import { queryKey, sleep } from '@tanstack/query-test-utils' import { lastValueFrom } from 'rxjs' @@ -34,7 +25,6 @@ import { provideTanStackQuery, } from '..' import { setSignalInputs } from './test-utils' -import type { CreateQueryOptions, OmitKeyof, QueryFunction } from '..' describe('injectQuery', () => { let queryCache: QueryCache @@ -56,218 +46,6 @@ describe('injectQuery', () => { vi.useRealTimers() }) - it('should return the correct types', () => { - const key = queryKey() - // unspecified query function should default to unknown - const noQueryFn = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - queryKey: key, - })), - ) - expectTypeOf(noQueryFn.data()).toEqualTypeOf() - expectTypeOf(noQueryFn.error()).toEqualTypeOf() - - // it should infer the result type from the query function - const fromQueryFn = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - queryKey: key, - queryFn: () => 'test', - })), - ) - expectTypeOf(fromQueryFn.data()).toEqualTypeOf() - expectTypeOf(fromQueryFn.error()).toEqualTypeOf() - - // it should be possible to specify the result type - const withResult = TestBed.runInInjectionContext(() => - 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 = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - queryKey: key, - queryFn: () => 'test', - })), - ) - expectTypeOf(withError.data()).toEqualTypeOf() - expectTypeOf(withError.error()).toEqualTypeOf() - - // it should infer the result type from the configuration - const withResultInfer = TestBed.runInInjectionContext(() => - 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 = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - queryKey: key, - queryFn: () => (Math.random() > 0.5 ? ('a' as const) : ('b' as const)), - })), - ) - expectTypeOf(unionTypeSync.data()).toEqualTypeOf<'a' | 'b' | undefined>() - const unionTypeAsync = TestBed.runInInjectionContext(() => - 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 - TestBed.runInInjectionContext(() => - // @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 = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - queryKey: key, - queryFn: () => queryFn(), - })), - ) - expectTypeOf(fromGenericQueryFn.data()).toEqualTypeOf() - expectTypeOf(fromGenericQueryFn.error()).toEqualTypeOf() - - // todo use query options? - const fromGenericOptionsQueryFn = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - 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 n + 42 - } - - const fromMyDataArrayKeyQueryFn = TestBed.runInInjectionContext(() => - 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 = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - queryKey: key, - queryFn: () => fetch('return Promise').then((resp) => resp.json()), - })), - ) - expectTypeOf(fromPromiseAnyQueryFn.data()).toEqualTypeOf() - - TestBed.runInInjectionContext(() => - effect(() => { - expect(fromMyDataArrayKeyQueryFn.data()).toBe(142) - }), - ) - - const getMyDataStringKey: QueryFunction = (context) => { - expectTypeOf(context.queryKey).toEqualTypeOf<['1']>() - return Number(context.queryKey[0]) + 42 - } - - const fromGetMyDataStringKeyQueryFn = TestBed.runInInjectionContext(() => - injectQuery(() => ({ - queryKey: ['1'] as ['1'], - queryFn: getMyDataStringKey, - })), - ) - expectTypeOf(fromGetMyDataStringKeyQueryFn.data()).toEqualTypeOf< - number | undefined - >() - - TestBed.runInInjectionContext(() => - effect(() => { - expect(fromGetMyDataStringKeyQueryFn.data()).toBe(43) - }), - ) - - // 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 = TestBed.runInInjectionContext(() => - 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 = TestBed.runInInjectionContext(() => - createWrappedFuncStyleQuery([''], () => Promise.resolve(true)), - ) - expectTypeOf(fromWrappedFuncStyleQuery.data()).toEqualTypeOf< - boolean | undefined - >() - }) - it('should return pending status initially', () => { const key = queryKey() const query = TestBed.runInInjectionContext(() => {