diff --git a/packages/vue-query/src/__tests__/queryClient.test-d.ts b/packages/vue-query/src/__tests__/queryClient.test-d.ts deleted file mode 100644 index 314fccdd5e..0000000000 --- a/packages/vue-query/src/__tests__/queryClient.test-d.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { describe, expectTypeOf, it } from 'vitest' -import { QueryClient } from '../queryClient' -import type { DataTag, InfiniteData } from '@tanstack/query-core' - -describe('getQueryData', () => { - it('should be typed if key is tagged', () => { - const queryKey = ['key'] as DataTag, number> - const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) - - expectTypeOf(data).toEqualTypeOf() - }) - - it('should infer unknown if key is not tagged', () => { - const queryKey = ['key'] as const - const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) - - expectTypeOf(data).toEqualTypeOf() - }) - - it('should infer passed generic if passed', () => { - const queryKey = ['key'] as const - const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) - - expectTypeOf(data).toEqualTypeOf() - }) - - it('should only allow Arrays to be passed', () => { - const queryKey = 'key' as const - const queryClient = new QueryClient() - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'QueryKey' - return queryClient.getQueryData(queryKey) - }) -}) - -describe('setQueryData', () => { - it('updater should be typed if key is tagged', () => { - const queryKey = ['key'] as DataTag, number> - const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { - expectTypeOf(prev).toEqualTypeOf() - return prev - }) - - expectTypeOf(data).toEqualTypeOf() - }) - - it('value should be typed if key is tagged', () => { - const queryKey = ['key'] as DataTag, number> - const queryClient = new QueryClient() - - // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, '1') - - // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, () => '1') - - const data = queryClient.setQueryData(queryKey, 1) - - expectTypeOf(data).toEqualTypeOf() - }) - - it('should infer unknown for updater if key is not tagged', () => { - const queryKey = ['key'] as const - const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { - expectTypeOf(prev).toEqualTypeOf() - return prev - }) - - expectTypeOf(data).toEqualTypeOf() - }) - - it('should infer unknown for value if key is not tagged', () => { - const queryKey = ['key'] as const - const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, 'foo') - - expectTypeOf(data).toEqualTypeOf() - }) - - it('should infer passed generic if passed', () => { - const queryKey = ['key'] as const - const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { - expectTypeOf(prev).toEqualTypeOf() - return prev - }) - - expectTypeOf(data).toEqualTypeOf() - }) - - it('should infer passed generic for value', () => { - const queryKey = ['key'] as const - const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, 'foo') - - expectTypeOf(data).toEqualTypeOf() - }) -}) - -describe('fetchInfiniteQuery', () => { - it('should allow passing pages', async () => { - const data = await new QueryClient().fetchInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, - initialPageParam: 1, - pages: 5, - }) - - expectTypeOf(data).toEqualTypeOf>() - }) - - it('should not allow passing getNextPageParam without pages', () => { - new QueryClient().fetchInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - initialPageParam: 1, - getNextPageParam: () => 1, - }) - }) - - it('should not allow passing pages without getNextPageParam', () => { - // @ts-expect-error Property 'getNextPageParam' is missing - new QueryClient().fetchInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - initialPageParam: 1, - pages: 5, - }) - }) -}) diff --git a/packages/vue-query/src/__tests__/queryClient.type.test.ts b/packages/vue-query/src/__tests__/queryClient.type.test.ts new file mode 100644 index 0000000000..e44948b2a0 --- /dev/null +++ b/packages/vue-query/src/__tests__/queryClient.type.test.ts @@ -0,0 +1,174 @@ +import { describe, it } from 'vitest' +import { QueryClient } from '../queryClient' +import { doNotExecute } from './test-utils' +import type { Equal, Expect } from './test-utils' +import type { DataTag, InfiniteData } from '@tanstack/query-core' + +describe('getQueryData', () => { + it('should be typed if key is tagged', () => { + doNotExecute(() => { + const queryKey = ['key'] as DataTag, number> + const queryClient = new QueryClient() + const data = queryClient.getQueryData(queryKey) + + const result: Expect> = true + return result + }) + }) + + it('should infer unknown if key is not tagged', () => { + doNotExecute(() => { + const queryKey = ['key'] as const + const queryClient = new QueryClient() + const data = queryClient.getQueryData(queryKey) + + const result: Expect> = true + return result + }) + }) + + it('should infer passed generic if passed', () => { + doNotExecute(() => { + const queryKey = ['key'] as const + const queryClient = new QueryClient() + const data = queryClient.getQueryData(queryKey) + + const result: Expect> = true + return result + }) + }) + + it('should only allow Arrays to be passed', () => { + doNotExecute(() => { + const queryKey = 'key' as const + const queryClient = new QueryClient() + // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'QueryKey' + return queryClient.getQueryData(queryKey) + }) + }) +}) + +describe('setQueryData', () => { + it('updater should be typed if key is tagged', () => { + doNotExecute(() => { + const queryKey = ['key'] as DataTag, number> + const queryClient = new QueryClient() + const data = queryClient.setQueryData(queryKey, (prev) => { + const result: Expect> = true + return result ? prev : 1 + }) + + const result: Expect> = true + return result + }) + }) + + it('value should be typed if key is tagged', () => { + doNotExecute(() => { + const queryKey = ['key'] as DataTag, number> + const queryClient = new QueryClient() + + // @ts-expect-error value should be a number + queryClient.setQueryData(queryKey, '1') + + // @ts-expect-error value should be a number + queryClient.setQueryData(queryKey, () => '1') + + const data = queryClient.setQueryData(queryKey, 1) + + const result: Expect> = true + return result + }) + }) + + it('should infer unknown for updater if key is not tagged', () => { + doNotExecute(() => { + const queryKey = ['key'] as const + const queryClient = new QueryClient() + const data = queryClient.setQueryData(queryKey, (prev) => { + const result: Expect> = true + return result ? prev : 1 + }) + + const result: Expect> = true + return result + }) + }) + + it('should infer unknown for value if key is not tagged', () => { + doNotExecute(() => { + const queryKey = ['key'] as const + const queryClient = new QueryClient() + const data = queryClient.setQueryData(queryKey, 'foo') + + const result: Expect> = true + return result + }) + }) + + it('should infer passed generic if passed', () => { + doNotExecute(() => { + const queryKey = ['key'] as const + const queryClient = new QueryClient() + const data = queryClient.setQueryData(queryKey, (prev) => { + const result: Expect> = true + return result ? prev : '1' + }) + + const result: Expect> = true + return result + }) + }) + + it('should infer passed generic for value', () => { + doNotExecute(() => { + const queryKey = ['key'] as const + const queryClient = new QueryClient() + const data = queryClient.setQueryData(queryKey, 'foo') + + const result: Expect> = true + return result + }) + }) +}) + +describe('fetchInfiniteQuery', () => { + it('should allow passing pages', () => { + doNotExecute(async () => { + const data = await new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + pages: 5, + }) + + const result: Expect>> = + true + return result + }) + }) + + it('should not allow passing getNextPageParam without pages', () => { + doNotExecute(async () => { + return new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + getNextPageParam: () => 1, + }) + }) + }) + + it('should not allow passing pages without getNextPageParam', () => { + doNotExecute(async () => { + // @ts-expect-error Property 'getNextPageParam' is missing + return new QueryClient().fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + pages: 5, + }) + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/queryOptions.test-d.ts b/packages/vue-query/src/__tests__/queryOptions.test-d.ts deleted file mode 100644 index 695db7f6e7..0000000000 --- a/packages/vue-query/src/__tests__/queryOptions.test-d.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { describe, expectTypeOf, it } from 'vitest' -import { reactive } from 'vue-demi' -import { dataTagSymbol } from '@tanstack/query-core' -import { QueryClient } from '../queryClient' -import { queryOptions } from '../queryOptions' -import { useQuery } from '../useQuery' - -describe('queryOptions', () => { - it('should not allow excess properties', () => { - queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - // @ts-expect-error this is a good error, because stallTime does not exist! - stallTime: 1000, - }) - }) - it('should infer types for callbacks', () => { - queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - staleTime: 1000, - select: (data) => { - expectTypeOf(data).toEqualTypeOf() - }, - }) - }) - it('should work when passed to useQuery', () => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const { data } = reactive(useQuery(options)) - - expectTypeOf(data).toEqualTypeOf() - }) - it('should tag the queryKey with the result type of the QueryFn', () => { - const { queryKey } = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf() - }) - it('should tag the queryKey even if no promise is returned', () => { - const { queryKey } = queryOptions({ - queryKey: ['key'], - queryFn: () => 5, - }) - - expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf() - }) - it('should tag the queryKey with unknown if there is no queryFn', () => { - const { queryKey } = queryOptions({ - queryKey: ['key'], - }) - - expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf() - }) - it('should tag the queryKey with the result type of the QueryFn if select is used', () => { - const { queryKey } = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - select: (data) => data.toString(), - }) - - expectTypeOf(queryKey[dataTagSymbol]).toEqualTypeOf() - }) - it('should return the proper type when passed to getQueryData', () => { - const { queryKey } = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) - - expectTypeOf(data).toEqualTypeOf() - }) - it('should properly type updaterFn when passed to setQueryData', () => { - const { queryKey } = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { - expectTypeOf(prev).toEqualTypeOf() - return prev - }) - - expectTypeOf(data).toEqualTypeOf() - }) - it('should properly type value when passed to setQueryData', () => { - const { queryKey } = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const queryClient = new QueryClient() - - // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, '5') - // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, () => '5') - - const data = queryClient.setQueryData(queryKey, 5) - - expectTypeOf(data).toEqualTypeOf() - }) -}) diff --git a/packages/vue-query/src/__tests__/queryOptions.types.test.ts b/packages/vue-query/src/__tests__/queryOptions.types.test.ts new file mode 100644 index 0000000000..e398760236 --- /dev/null +++ b/packages/vue-query/src/__tests__/queryOptions.types.test.ts @@ -0,0 +1,151 @@ +import { describe, it } from 'vitest' +import { reactive } from 'vue-demi' +import { QueryClient } from '../queryClient' +import { queryOptions } from '../queryOptions' +import { useQuery } from '../useQuery' +import { doNotExecute } from './test-utils' +import type { dataTagSymbol } from '@tanstack/query-core' +import type { Equal, Expect } from './test-utils' + +describe('queryOptions', () => { + it('should not allow excess properties', () => { + doNotExecute(() => { + return queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + // @ts-expect-error this is a good error, because stallTime does not exist! + stallTime: 1000, + }) + }) + }) + it('should infer types for callbacks', () => { + doNotExecute(() => { + return queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + staleTime: 1000, + select: (data) => { + const result: Expect> = true + return result + }, + }) + }) + }) + it('should work when passed to useQuery', () => { + doNotExecute(() => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const { data } = reactive(useQuery(options)) + + const result: Expect> = true + + return result + }) + }) + it('should tag the queryKey with the result type of the QueryFn', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const result: Expect< + Equal<(typeof queryKey)[typeof dataTagSymbol], number> + > = true + return result + }) + }) + it('should tag the queryKey even if no promise is returned', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => 5, + }) + + const result: Expect< + Equal<(typeof queryKey)[typeof dataTagSymbol], number> + > = true + return result + }) + }) + it('should tag the queryKey with unknown if there is no queryFn', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + }) + + const result: Expect< + Equal<(typeof queryKey)[typeof dataTagSymbol], unknown> + > = true + return result + }) + }) + it('should tag the queryKey with the result type of the QueryFn if select is used', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const result: Expect< + Equal<(typeof queryKey)[typeof dataTagSymbol], number> + > = true + return result + }) + }) + it('should return the proper type when passed to getQueryData', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const queryClient = new QueryClient() + const data = queryClient.getQueryData(queryKey) + + const result: Expect> = true + return result + }) + }) + it('should properly type updaterFn when passed to setQueryData', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const queryClient = new QueryClient() + const data = queryClient.setQueryData(queryKey, (prev) => { + const result: Expect> = true + return result ? prev : 1 + }) + + const result: Expect> = true + return result + }) + }) + it('should properly type value when passed to setQueryData', () => { + doNotExecute(() => { + const { queryKey } = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const queryClient = new QueryClient() + + // @ts-expect-error value should be a number + queryClient.setQueryData(queryKey, '5') + // @ts-expect-error value should be a number + queryClient.setQueryData(queryKey, () => '5') + + const data = queryClient.setQueryData(queryKey, 5) + + const result: Expect> = true + return result + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/test-utils.ts b/packages/vue-query/src/__tests__/test-utils.ts index 1d71090f29..3517bd3690 100644 --- a/packages/vue-query/src/__tests__/test-utils.ts +++ b/packages/vue-query/src/__tests__/test-utils.ts @@ -50,3 +50,13 @@ export function successMutator(param: T): Promise { export function errorMutator(_: T): Promise { return rejectFetcher() } + +export type Equal = (() => T extends TTargetA + ? 1 + : 2) extends () => T extends TTargetB ? 1 : 2 + ? true + : false + +export type Expect = T + +export const doNotExecute = (_func: () => void) => true diff --git a/packages/vue-query/src/__tests__/useInfiniteQuery.test-d.tsx b/packages/vue-query/src/__tests__/useInfiniteQuery.test-d.tsx deleted file mode 100644 index 583299e141..0000000000 --- a/packages/vue-query/src/__tests__/useInfiniteQuery.test-d.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { describe, expectTypeOf, it } from 'vitest' -import { reactive } from 'vue-demi' -import { useInfiniteQuery } from '../useInfiniteQuery' -import { simpleFetcher } from './test-utils' -import type { InfiniteData } from '@tanstack/query-core' - -describe('Discriminated union return type', () => { - it('data should be possibly undefined by default', () => { - const query = reactive( - useInfiniteQuery({ - queryKey: ['infiniteQuery'], - queryFn: simpleFetcher, - getNextPageParam: () => undefined, - initialPageParam: 0, - }), - ) - - expectTypeOf(query.data).toEqualTypeOf< - // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now - InfiniteData | undefined - >() - }) - - it('data should be defined when query is success', () => { - const query = reactive( - useInfiniteQuery({ - queryKey: ['infiniteQuery'], - queryFn: simpleFetcher, - getNextPageParam: () => undefined, - initialPageParam: 0, - }), - ) - - if (query.isSuccess) { - // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now - expectTypeOf(query.data).toEqualTypeOf>() - } - }) - - it('error should be null when query is success', () => { - const query = reactive( - useInfiniteQuery({ - queryKey: ['infiniteQuery'], - queryFn: simpleFetcher, - getNextPageParam: () => undefined, - initialPageParam: 0, - }), - ) - - if (query.isSuccess) { - expectTypeOf(query.error).toEqualTypeOf() - } - }) - - it('data should be undefined when query is pending', () => { - const query = reactive( - useInfiniteQuery({ - queryKey: ['infiniteQuery'], - queryFn: simpleFetcher, - getNextPageParam: () => undefined, - initialPageParam: 0, - }), - ) - - if (query.isPending) { - expectTypeOf(query.data).toEqualTypeOf() - } - }) - - it('error should be defined when query is error', () => { - const query = reactive( - useInfiniteQuery({ - queryKey: ['infiniteQuery'], - queryFn: simpleFetcher, - getNextPageParam: () => undefined, - initialPageParam: 0, - }), - ) - - if (query.isError) { - expectTypeOf(query.error).toEqualTypeOf() - } - }) -}) diff --git a/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx b/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx new file mode 100644 index 0000000000..7e164f262c --- /dev/null +++ b/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx @@ -0,0 +1,106 @@ +import { describe, it } from 'vitest' +import { reactive } from 'vue-demi' +import { useInfiniteQuery } from '../useInfiniteQuery' +import { doNotExecute, simpleFetcher } from './test-utils' +import type { Equal, Expect } from './test-utils' +import type { InfiniteData } from '@tanstack/query-core' + +describe('Discriminated union return type', () => { + it('data should be possibly undefined by default', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryKey: ['infiniteQuery'], + queryFn: simpleFetcher, + getNextPageParam: () => undefined, + initialPageParam: 0, + }), + ) + + // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now + const result: Expect< + Equal | undefined, typeof query.data> + > = true + return result + }) + }) + + it('data should be defined when query is success', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryKey: ['infiniteQuery'], + queryFn: simpleFetcher, + getNextPageParam: () => undefined, + initialPageParam: 0, + }), + ) + + if (query.isSuccess) { + // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now + const result: Expect< + Equal, typeof query.data> + > = true + return result + } + return + }) + }) + + it('error should be null when query is success', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryKey: ['infiniteQuery'], + queryFn: simpleFetcher, + getNextPageParam: () => undefined, + initialPageParam: 0, + }), + ) + + if (query.isSuccess) { + const result: Expect> = true + return result + } + return + }) + }) + + it('data should be undefined when query is pending', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryKey: ['infiniteQuery'], + queryFn: simpleFetcher, + getNextPageParam: () => undefined, + initialPageParam: 0, + }), + ) + + if (query.isPending) { + const result: Expect> = true + return result + } + return + }) + }) + + it('error should be defined when query is error', () => { + doNotExecute(() => { + const query = reactive( + useInfiniteQuery({ + queryKey: ['infiniteQuery'], + queryFn: simpleFetcher, + getNextPageParam: () => undefined, + initialPageParam: 0, + }), + ) + + if (query.isError) { + const result: Expect> = true + return result + } + return + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/useMutation.test-d.tsx b/packages/vue-query/src/__tests__/useMutation.test-d.tsx deleted file mode 100644 index d759e9ad6b..0000000000 --- a/packages/vue-query/src/__tests__/useMutation.test-d.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { describe, expectTypeOf, it } from 'vitest' -import { reactive } from 'vue-demi' -import { useMutation } from '../useMutation' -import { successMutator } from './test-utils' - -describe('Discriminated union return type', () => { - it('data should be possibly undefined by default', () => { - const mutation = reactive( - useMutation({ mutationFn: successMutator }), - ) - - expectTypeOf(mutation.data).toEqualTypeOf() - }) - - it('data should be defined when mutation is success', () => { - const mutation = reactive( - useMutation({ mutationFn: successMutator }), - ) - - if (mutation.isSuccess) { - expectTypeOf(mutation.data).toEqualTypeOf() - } - }) - - it('error should be null when mutation is success', () => { - const mutation = reactive( - useMutation({ mutationFn: successMutator }), - ) - - if (mutation.isSuccess) { - expectTypeOf(mutation.error).toEqualTypeOf() - } - }) - - it('data should be undefined when mutation is pending', () => { - const mutation = reactive( - useMutation({ mutationFn: successMutator }), - ) - - if (mutation.isPending) { - expectTypeOf(mutation.data).toEqualTypeOf() - } - }) - - it('error should be defined when mutation is error', () => { - const mutation = reactive( - useMutation({ mutationFn: successMutator }), - ) - - if (mutation.isError) { - expectTypeOf(mutation.error).toEqualTypeOf() - } - }) - - it('should narrow variables', () => { - const mutation = reactive( - useMutation({ mutationFn: successMutator }), - ) - - if (mutation.isIdle) { - expectTypeOf(mutation.variables).toEqualTypeOf() - } - if (mutation.isPending) { - expectTypeOf(mutation.variables).toEqualTypeOf() - } - if (mutation.isSuccess) { - expectTypeOf(mutation.variables).toEqualTypeOf() - } - expectTypeOf(mutation.variables).toEqualTypeOf() - }) -}) diff --git a/packages/vue-query/src/__tests__/useMutation.types.test.tsx b/packages/vue-query/src/__tests__/useMutation.types.test.tsx new file mode 100644 index 0000000000..00fcea130a --- /dev/null +++ b/packages/vue-query/src/__tests__/useMutation.types.test.tsx @@ -0,0 +1,98 @@ +import { describe, it } from 'vitest' +import { reactive } from 'vue-demi' +import { useMutation } from '../useMutation' +import { doNotExecute, successMutator } from './test-utils' +import type { Equal, Expect } from './test-utils' + +describe('Discriminated union return type', () => { + it('data should be possibly undefined by default', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + const result: Expect> = + true + return result + }) + }) + + it('data should be defined when mutation is success', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isSuccess) { + const result: Expect> = true + return result + } + return + }) + }) + + it('error should be null when mutation is success', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isSuccess) { + const result: Expect> = true + return result + } + return + }) + }) + + it('data should be undefined when mutation is pending', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isPending) { + const result: Expect> = true + return result + } + return + }) + }) + + it('error should be defined when mutation is error', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isError) { + const result: Expect> = true + return result + } + return + }) + }) + + it('should narrow variables', () => { + doNotExecute(() => { + const mutation = reactive( + useMutation({ mutationFn: successMutator }), + ) + + if (mutation.isIdle) { + const result: Expect> = true + return result + } + if (mutation.isPending) { + const result: Expect> = true + return result + } + if (mutation.isSuccess) { + const result: Expect> = true + return result + } + const result: Expect> = true + return result + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/useQueries.test-d.ts b/packages/vue-query/src/__tests__/useQueries.test-d.ts deleted file mode 100644 index 3177931ed1..0000000000 --- a/packages/vue-query/src/__tests__/useQueries.test-d.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, expectTypeOf, it } from 'vitest' -import { reactive } from 'vue' -import { useQueries } from '..' -import { queryOptions } from '../queryOptions' -import type { UseQueryOptions } from '../useQuery' - -describe('UseQueries config object overload', () => { - it('TData should always be defined when initialData is provided as an object', () => { - const query1 = { - queryKey: ['key1'], - queryFn: () => ({ wow: true }), - initialData: { wow: false }, - } - - const query2 = queryOptions({ - queryKey: ['key2'], - queryFn: () => 'Query Data', - initialData: 'initial data', - }) - - const query3 = { - queryKey: ['key2'], - queryFn: () => 'Query Data', - } - - const { value: queriesState } = useQueries({ - queries: [query1, query2, query3], - }) - - const query1Data = queriesState[0].data - const query2Data = queriesState[1].data - const query3Data = queriesState[2].data - - expectTypeOf(query1Data).toEqualTypeOf<{ wow: boolean }>() - expectTypeOf(query2Data).toEqualTypeOf() - expectTypeOf(query3Data).toEqualTypeOf() - }) - - it('TData should be defined when passed through queryOptions', () => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: { wow: true }, - }) - - const { value: queriesState } = useQueries({ queries: [options] }) - - const data = queriesState[0].data - - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() - }) - - it('it should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQueries', () => { - 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 - - expectTypeOf(query1Data).toEqualTypeOf() - expectTypeOf(query2Data).toEqualTypeOf() - }) - - it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => { - const { value: queriesState } = useQueries({ - queries: [ - { - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: () => undefined as { wow: boolean } | undefined, - }, - ], - }) - - const data = queriesState[0].data - - expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>() - }) - - describe('custom hook', () => { - it('should allow custom hooks using UseQueryOptions', () => { - type Data = string - - const useCustomQueries = ( - options?: Omit, 'queryKey' | 'queryFn'>, - ) => { - return useQueries({ - queries: [ - { - ...options, - queryKey: ['todos-key'], - queryFn: () => Promise.resolve('data'), - }, - ], - }) - } - - const { value: queriesState } = useCustomQueries() - const data = queriesState[0].data - - expectTypeOf(data).toEqualTypeOf() - }) - }) -}) diff --git a/packages/vue-query/src/__tests__/useQueries.types.test.ts b/packages/vue-query/src/__tests__/useQueries.types.test.ts new file mode 100644 index 0000000000..0d3b67f84e --- /dev/null +++ b/packages/vue-query/src/__tests__/useQueries.types.test.ts @@ -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> = true + + const result2: Expect> = true + + const result3: Expect> = 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> = 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> = + true + const result2: Expect> = + 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> = + true + return result + }) + }) + + describe('custom hook', () => { + it('should allow custom hooks using UseQueryOptions', () => { + doNotExecute(() => { + type Data = string + + const useCustomQueries = ( + options?: Omit, '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> = true + return result + }) + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/useQuery.test-d.ts b/packages/vue-query/src/__tests__/useQuery.test-d.ts deleted file mode 100644 index d329291208..0000000000 --- a/packages/vue-query/src/__tests__/useQuery.test-d.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { describe, expectTypeOf, it } from 'vitest' -import { reactive } from 'vue-demi' -import { useQuery } from '../useQuery' -import { queryOptions } from '../queryOptions' -import { simpleFetcher } from './test-utils' -import type { UseQueryOptions } from '../useQuery' - -describe('initialData', () => { - describe('Config object overload', () => { - it('TData should always be defined when initialData is provided as an object', () => { - const { data } = reactive( - useQuery({ - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: { wow: true }, - }), - ) - - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() - }) - - it('TData should be defined when passed through queryOptions', () => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: { wow: true }, - }) - const { data } = reactive(useQuery(options)) - - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() - }) - - it('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), - }) - - const query = reactive( - 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 } = reactive( - useQuery({ - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: () => ({ wow: true }), - }), - ) - - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() - }) - - it('TData should have undefined in the union when initialData is NOT provided', () => { - const { data } = reactive( - useQuery({ - queryKey: ['key'], - queryFn: () => { - return { - wow: true, - } - }, - }), - ) - - 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 } = reactive( - useQuery({ - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: () => undefined as { wow: boolean } | undefined, - }), - ) - - expectTypeOf(data).toEqualTypeOf<{ 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 } = reactive( - useQuery({ - queryKey: ['key'], - queryFn: () => ({ wow: true }), - initialData: () => undefined as { wow: boolean } | undefined, - }), - ) - - if (isSuccess) { - expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() - } - }) - }) - - describe('custom composable', () => { - it('should allow custom composable using UseQueryOptions', () => { - type Data = string - - const useCustomQuery = ( - options?: Omit, 'queryKey' | 'queryFn'>, - ) => { - return useQuery({ - ...options, - queryKey: ['todos-key'], - queryFn: () => Promise.resolve('data'), - }) - } - - const { data } = reactive(useCustomQuery()) - - expectTypeOf(data).toEqualTypeOf() - }) - }) - - describe('structuralSharing', () => { - it('should restrict to same types', () => { - useQuery({ - queryKey: ['key'], - queryFn: () => 5, - structuralSharing: (_oldData, newData) => newData, - }) - }) - }) - - describe('Discriminated union return type', () => { - it('data should be possibly undefined by default', () => { - const query = reactive( - useQuery({ - queryKey: ['key'], - queryFn: simpleFetcher, - }), - ) - - expectTypeOf(query.data).toEqualTypeOf() - }) - - it('data should be defined when query is success', () => { - const query = reactive( - useQuery({ - queryKey: ['key'], - queryFn: simpleFetcher, - }), - ) - - if (query.isSuccess) { - expectTypeOf(query.data).toEqualTypeOf() - } - }) - - it('error should be null when query is success', () => { - const query = reactive( - useQuery({ - queryKey: ['key'], - queryFn: simpleFetcher, - }), - ) - - if (query.isSuccess) { - expectTypeOf(query.error).toEqualTypeOf() - } - }) - - it('data should be undefined when query is pending', () => { - const query = reactive( - useQuery({ - queryKey: ['key'], - queryFn: simpleFetcher, - }), - ) - - if (query.isPending) { - expectTypeOf(query.data).toEqualTypeOf() - } - }) - - it('error should be defined when query is error', () => { - const query = reactive( - useQuery({ - queryKey: ['key'], - queryFn: simpleFetcher, - }), - ) - - if (query.isError) { - expectTypeOf(query.error).toEqualTypeOf() - } - }) - }) -}) diff --git a/packages/vue-query/src/__tests__/useQuery.types.test.ts b/packages/vue-query/src/__tests__/useQuery.types.test.ts new file mode 100644 index 0000000000..abf587f5d5 --- /dev/null +++ b/packages/vue-query/src/__tests__/useQuery.types.test.ts @@ -0,0 +1,277 @@ +import { describe, it } from 'vitest' +import { reactive } from 'vue-demi' +import { useQuery } from '../useQuery' +import { queryOptions } from '../queryOptions' +import { doNotExecute, simpleFetcher } from './test-utils' +import type { UseQueryOptions } from '../useQuery' +import type { Equal, Expect } from './test-utils' + +describe('initialData', () => { + describe('Config object overload', () => { + it('TData should always be defined when initialData is provided as an object', () => { + doNotExecute(() => { + const { data } = reactive( + useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: { + wow: true, + }, + }), + ) + + const result: Expect> = true + + return result + }) + }) + + it('TData should be defined when passed through queryOptions', () => { + doNotExecute(() => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: { + wow: true, + }, + }) + const { data } = reactive(useQuery(options)) + + const result: Expect> = true + return result + }) + }) + + it('it should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQuery', () => { + doNotExecute(() => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(1), + }) + + const query = reactive( + useQuery({ + ...options, + select: (data) => data > 1, + }), + ) + + const result: Expect> = + true + return result + }) + }) + + it('TData should always be defined when initialData is provided as a function which ALWAYS returns the data', () => { + doNotExecute(() => { + const { data } = reactive( + useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => ({ + wow: true, + }), + }), + ) + + const result: Expect> = true + return result + }) + }) + + it('TData should have undefined in the union when initialData is NOT provided', () => { + doNotExecute(() => { + const { data } = reactive( + useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + }), + ) + + const result: Expect> = + true + return result + }) + }) + + it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => { + doNotExecute(() => { + const { data } = reactive( + useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }), + ) + + const result: Expect> = + true + return result + }) + }) + + it('TData should be narrowed after an isSuccess check when initialData is provided as a function which can return undefined', () => { + doNotExecute(() => { + const { data, isSuccess } = reactive( + useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }), + ) + + if (isSuccess) { + const result: Expect> = true + return result + } + return false + }) + }) + }) + + describe('custom composable', () => { + it('should allow custom composable using UseQueryOptions', () => { + doNotExecute(() => { + type Data = string + + const useCustomQuery = ( + options?: Omit, 'queryKey' | 'queryFn'>, + ) => { + return useQuery({ + ...options, + queryKey: ['todos-key'], + queryFn: () => Promise.resolve('data'), + }) + } + + const { data } = reactive(useCustomQuery()) + + const result: Expect> = true + return result + }) + }) + }) + + describe('structuralSharing', () => { + it('should restrict to same types', () => { + doNotExecute(() => { + useQuery({ + queryKey: ['key'], + queryFn: () => 5, + structuralSharing: (_oldData, newData) => { + return newData + }, + }) + }) + }) + }) + + describe('Discriminated union return type', () => { + it('data should be possibly undefined by default', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryKey: ['key'], + queryFn: simpleFetcher, + }), + ) + + const result: Expect> = + true + return result + }) + }) + + it('data should be defined when query is success', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryKey: ['key'], + queryFn: simpleFetcher, + }), + ) + + if (query.isSuccess) { + const result: Expect> = true + return result + } + return + }) + }) + + it('error should be null when query is success', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryKey: ['key'], + queryFn: simpleFetcher, + }), + ) + + if (query.isSuccess) { + const result: Expect> = true + return result + } + return + }) + }) + + it('data should be undefined when query is pending', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryKey: ['key'], + queryFn: simpleFetcher, + }), + ) + + if (query.isPending) { + const result: Expect> = true + return result + } + return + }) + }) + + it('error should be defined when query is error', () => { + doNotExecute(() => { + const query = reactive( + useQuery({ + queryKey: ['key'], + queryFn: simpleFetcher, + }), + ) + + if (query.isError) { + const result: Expect> = true + return result + } + return + }) + }) + }) +})