From 92c1d3f4ae397a2318e1af8580215979b34443b4 Mon Sep 17 00:00:00 2001 From: Jonghyeon Ko Date: Wed, 28 Feb 2024 03:02:27 +0900 Subject: [PATCH] test(react-query): use vitest typecheck correctly with *.test-d.ts --- package.json | 4 +- .../__tests__/infiniteQueryOptions.test-d.tsx | 142 +++++++++++ .../infiniteQueryOptions.types.test.tsx | 177 -------------- .../src/__tests__/queryOptions.test-d.tsx | 140 +++++++++++ .../src/__tests__/queryOptions.types.test.tsx | 192 --------------- .../src/__tests__/suspense.test-d.tsx | 124 ++++++++++ .../src/__tests__/suspense.types.test.tsx | 147 ------------ .../src/__tests__/useInfiniteQuery.test-d.tsx | 140 +++++++++++ .../__tests__/useInfiniteQuery.type.test.tsx | 224 ------------------ .../src/__tests__/useQueries.test-d.tsx | 126 ++++++++++ .../src/__tests__/useQueries.types.test.tsx | 148 ------------ .../src/__tests__/useQuery.test-d.tsx | 140 +++++++++++ .../src/__tests__/useQuery.types.test.tsx | 179 -------------- packages/react-query/src/__tests__/utils.tsx | 8 - pnpm-lock.yaml | 90 +++---- 15 files changed, 864 insertions(+), 1117 deletions(-) create mode 100644 packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx delete mode 100644 packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx create mode 100644 packages/react-query/src/__tests__/queryOptions.test-d.tsx delete mode 100644 packages/react-query/src/__tests__/queryOptions.types.test.tsx create mode 100644 packages/react-query/src/__tests__/suspense.test-d.tsx delete mode 100644 packages/react-query/src/__tests__/suspense.types.test.tsx create mode 100644 packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx delete mode 100644 packages/react-query/src/__tests__/useInfiniteQuery.type.test.tsx create mode 100644 packages/react-query/src/__tests__/useQueries.test-d.tsx delete mode 100644 packages/react-query/src/__tests__/useQueries.types.test.tsx create mode 100644 packages/react-query/src/__tests__/useQuery.test-d.tsx delete mode 100644 packages/react-query/src/__tests__/useQuery.types.test.tsx diff --git a/package.json b/package.json index 33173dbbc6..31e80ca29b 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.20.0", - "@vitest/coverage-istanbul": "^1.2.2", + "@vitest/coverage-istanbul": "^1.3.1", "cpy-cli": "^5.0.0", "esbuild-plugin-file-path-extensions": "^2.0.0", "eslint": "^8.56.0", @@ -73,7 +73,7 @@ "tsup": "^8.0.1", "typescript": "5.2.2", "vite": "^5.1.1", - "vitest": "^1.2.2" + "vitest": "^1.3.1" }, "pnpm": { "overrides": { diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx new file mode 100644 index 0000000000..434f5fb48c --- /dev/null +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -0,0 +1,142 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import { infiniteQueryOptions } from '../infiniteQueryOptions' +import { useInfiniteQuery } from '../useInfiniteQuery' +import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery' +import type { InfiniteData, dataTagSymbol } from '@tanstack/query-core' + +describe('queryOptions', () => { + it('should not allow excess properties', () => { + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('data'), + getNextPageParam: () => 1, + initialPageParam: 1, + // @ts-expect-error this is a good error, because stallTime does not exist! + stallTime: 1000, + }) + }) + it('should infer types for callbacks', () => { + infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('data'), + staleTime: 1000, + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => { + expectTypeOf(data).toEqualTypeOf>() + }, + }) + }) + it('should work when passed to useInfiniteQuery', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const { data } = useInfiniteQuery(options) + + // known issue: type of pageParams is unknown when returned from useInfiniteQuery + expectTypeOf(data).toEqualTypeOf< + InfiniteData | undefined + >() + }) + it('should work when passed to useSuspenseInfiniteQuery', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const { data } = useSuspenseInfiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should work when passed to fetchInfiniteQuery', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const data = await new QueryClient().fetchInfiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should tag the queryKey with the result type of the QueryFn', () => { + const { queryKey } = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + expectTypeOf<(typeof queryKey)[typeof dataTagSymbol]>().toEqualTypeOf< + InfiniteData + >() + }) + it('should tag the queryKey even if no promise is returned', () => { + const { queryKey } = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => 'string', + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + expectTypeOf<(typeof queryKey)[typeof dataTagSymbol]>().toEqualTypeOf< + InfiniteData + >() + }) + it('should tag the queryKey with the result type of the QueryFn if select is used', () => { + const { queryKey } = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + select: (data) => data.pages, + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + expectTypeOf<(typeof queryKey)[typeof dataTagSymbol]>().toEqualTypeOf< + InfiniteData + >() + }) + it('should return the proper type when passed to getQueryData', () => { + const { queryKey } = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const queryClient = new QueryClient() + const data = queryClient.getQueryData(queryKey) + + expectTypeOf(data).toEqualTypeOf< + InfiniteData | undefined + >() + }) + it('should properly type when passed to setQueryData', () => { + const { queryKey } = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const queryClient = new QueryClient() + const data = queryClient.setQueryData(queryKey, (prev) => { + expectTypeOf(prev).toEqualTypeOf< + InfiniteData | undefined + >() + return prev + }) + + expectTypeOf(data).toEqualTypeOf< + InfiniteData | undefined + >() + }) +}) diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx deleted file mode 100644 index 0bf8fcc579..0000000000 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.types.test.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { describe, it } from 'vitest' -import { QueryClient } from '@tanstack/query-core' -import { infiniteQueryOptions } from '../infiniteQueryOptions' -import { useInfiniteQuery } from '../useInfiniteQuery' -import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery' -import { doNotExecute } from './utils' -import type { InfiniteData, dataTagSymbol } from '@tanstack/query-core' -import type { Equal, Expect } from './utils' - -describe('queryOptions', () => { - it('should not allow excess properties', () => { - doNotExecute(() => { - return infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('data'), - getNextPageParam: () => 1, - initialPageParam: 1, - // @ts-expect-error this is a good error, because stallTime does not exist! - stallTime: 1000, - }) - }) - }) - it('should infer types for callbacks', () => { - doNotExecute(() => { - return infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('data'), - staleTime: 1000, - getNextPageParam: () => 1, - initialPageParam: 1, - select: (data) => { - const result: Expect< - Equal, typeof data> - > = true - return result - }, - }) - }) - }) - it('should work when passed to useInfiniteQuery', () => { - doNotExecute(() => { - const options = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const { data } = useInfiniteQuery(options) - - // known issue: type of pageParams is unknown when returned from useInfiniteQuery - const result: Expect< - Equal | undefined> - > = true - return result - }) - }) - it('should work when passed to useSuspenseInfiniteQuery', () => { - doNotExecute(() => { - const options = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const { data } = useSuspenseInfiniteQuery(options) - - const result: Expect>> = - true - return result - }) - }) - it('should work when passed to fetchInfiniteQuery', () => { - doNotExecute(async () => { - const options = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const data = await new QueryClient().fetchInfiniteQuery(options) - - const result: Expect>> = - true - return result - }) - }) - it('should tag the queryKey with the result type of the QueryFn', () => { - doNotExecute(() => { - const { queryKey } = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], InfiniteData> - > = true - return result - }) - }) - it('should tag the queryKey even if no promise is returned', () => { - doNotExecute(() => { - const { queryKey } = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => 'string', - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], InfiniteData> - > = true - return result - }) - }) - it('should tag the queryKey with the result type of the QueryFn if select is used', () => { - doNotExecute(() => { - const { queryKey } = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - select: (data) => data.pages, - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const result: Expect< - Equal<(typeof queryKey)[typeof dataTagSymbol], InfiniteData> - > = true - return result - }) - }) - it('should return the proper type when passed to getQueryData', () => { - doNotExecute(() => { - const { queryKey } = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) - - const result: Expect< - Equal | undefined> - > = true - return result - }) - }) - it('should properly type when passed to setQueryData', () => { - doNotExecute(() => { - const { queryKey } = infiniteQueryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve('string'), - getNextPageParam: () => 1, - initialPageParam: 1, - }) - - const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { - const result: Expect< - Equal | undefined> - > = true - return result ? prev : { pages: ['foo'], pageParams: [1] } - }) - - const result: Expect< - Equal | undefined> - > = true - return result - }) - }) -}) diff --git a/packages/react-query/src/__tests__/queryOptions.test-d.tsx b/packages/react-query/src/__tests__/queryOptions.test-d.tsx new file mode 100644 index 0000000000..23259bc169 --- /dev/null +++ b/packages/react-query/src/__tests__/queryOptions.test-d.tsx @@ -0,0 +1,140 @@ +import { describe, expect, expectTypeOf, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import { dataTagSymbol } from '@tanstack/query-core' +import { queryOptions } from '../queryOptions' +import { useQuery } from '../useQuery' +import { useQueries } from '../useQueries' +import { useSuspenseQuery } from '../useSuspenseQuery' + +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 } = useQuery(options) + expectTypeOf(data).toEqualTypeOf() + }) + it('should work when passed to useSuspenseQuery', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const { data } = useSuspenseQuery(options) + expectTypeOf(data).toEqualTypeOf() + }) + it('should work when passed to fetchQuery', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const data = await new QueryClient().fetchQuery(options) + expectTypeOf(data).toEqualTypeOf() + }) + it('should work when passed to useQueries', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const [{ data }] = useQueries({ + queries: [options], + }) + + expectTypeOf(data).toEqualTypeOf() + }) + it('should tag the queryKey with the result type of the QueryFn', () => { + expect(() => { + 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/react-query/src/__tests__/queryOptions.types.test.tsx b/packages/react-query/src/__tests__/queryOptions.types.test.tsx deleted file mode 100644 index f15234ec59..0000000000 --- a/packages/react-query/src/__tests__/queryOptions.types.test.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { describe, it } from 'vitest' -import { QueryClient } from '@tanstack/query-core' -import { queryOptions } from '../queryOptions' -import { useQuery } from '../useQuery' -import { useQueries } from '../useQueries' -import { useSuspenseQuery } from '../useSuspenseQuery' -import { doNotExecute } from './utils' -import type { dataTagSymbol } from '@tanstack/query-core' -import type { Equal, Expect } from './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 } = useQuery(options) - - const result: Expect> = true - return result - }) - }) - it('should work when passed to useSuspenseQuery', () => { - doNotExecute(() => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const { data } = useSuspenseQuery(options) - - const result: Expect> = true - return result - }) - }) - it('should work when passed to fetchQuery', () => { - doNotExecute(async () => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const data = await new QueryClient().fetchQuery(options) - - const result: Expect> = true - return result - }) - }) - it('should work when passed to useQueries', () => { - doNotExecute(() => { - const options = queryOptions({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const [{ data }] = useQueries({ - queries: [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/react-query/src/__tests__/suspense.test-d.tsx b/packages/react-query/src/__tests__/suspense.test-d.tsx new file mode 100644 index 0000000000..9367866753 --- /dev/null +++ b/packages/react-query/src/__tests__/suspense.test-d.tsx @@ -0,0 +1,124 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { useSuspenseQuery } from '../useSuspenseQuery' +import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery' +import type { InfiniteData } from '@tanstack/query-core' + +describe('useSuspenseQuery', () => { + it('should always have data defined', () => { + const { data } = useSuspenseQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + expectTypeOf(data).toEqualTypeOf() + }) + + it('should not have pending status', () => { + const { status } = useSuspenseQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + expectTypeOf(status).toEqualTypeOf<'error' | 'success'>() + }) + + it('should not allow placeholderData, enabled or throwOnError props', () => { + useSuspenseQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + // @ts-expect-error TS2345 + placeholderData: 5, + enabled: true, + }) + + useSuspenseQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + // @ts-expect-error TS2345 + enabled: true, + }) + + useSuspenseQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + // @ts-expect-error TS2345 + throwOnError: true, + }) + }) + + it('should not return isPlaceholderData', () => { + const query = useSuspenseQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + // @ts-expect-error TS2339 + query.isPlaceholderData + }) +}) + +describe('useSuspenseInfiniteQuery', () => { + it('should always have data defined', () => { + const { data } = useSuspenseInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + }) + + expectTypeOf(data).toEqualTypeOf>() + }) + + it('should not have pending status', () => { + const { status } = useSuspenseInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + }) + + expectTypeOf(status).toEqualTypeOf<'error' | 'success'>() + }) + + it('should not allow placeholderData, enabled or throwOnError props', () => { + useSuspenseInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error TS2345 + placeholderData: 5, + enabled: true, + }) + + useSuspenseInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error TS2345 + enabled: true, + }) + + useSuspenseInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + // @ts-expect-error TS2345 + throwOnError: true, + }) + }) + + it('should not return isPlaceholderData', () => { + const query = useSuspenseInfiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + initialPageParam: 1, + getNextPageParam: () => 1, + }) + + // @ts-expect-error TS2339 + query.isPlaceholderData + }) +}) diff --git a/packages/react-query/src/__tests__/suspense.types.test.tsx b/packages/react-query/src/__tests__/suspense.types.test.tsx deleted file mode 100644 index 6afb9aaaeb..0000000000 --- a/packages/react-query/src/__tests__/suspense.types.test.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { describe, it } from 'vitest' -import { useSuspenseQuery } from '../useSuspenseQuery' -import { useSuspenseInfiniteQuery } from '../useSuspenseInfiniteQuery' -import { doNotExecute } from './utils' -import type { InfiniteData } from '@tanstack/query-core' -import type { Equal, Expect } from './utils' - -describe('useSuspenseQuery', () => { - it('should always have data defined', () => { - doNotExecute(() => { - const { data } = useSuspenseQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const result: Expect> = true - return result - }) - }) - - it('should not have pending status', () => { - doNotExecute(() => { - const { status } = useSuspenseQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - const result: Expect> = true - return result - }) - }) - - it('should not allow placeholderData, enabled or throwOnError props', () => { - doNotExecute(() => { - useSuspenseQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - // @ts-expect-error TS2345 - placeholderData: 5, - enabled: true, - }) - - useSuspenseQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - // @ts-expect-error TS2345 - enabled: true, - }) - - useSuspenseQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - // @ts-expect-error TS2345 - throwOnError: true, - }) - }) - }) - - it('should not return isPlaceholderData', () => { - doNotExecute(() => { - const query = useSuspenseQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - }) - - // @ts-expect-error TS2339 - void query.isPlaceholderData - }) - }) -}) - -describe('useSuspenseInfiniteQuery', () => { - it('should always have data defined', () => { - doNotExecute(() => { - const { data } = useSuspenseInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - }) - - const result: Expect>> = - true - return result - }) - }) - - it('should not have pending status', () => { - doNotExecute(() => { - const { status } = useSuspenseInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - }) - - const result: Expect> = true - return result - }) - }) - - it('should not allow placeholderData, enabled or throwOnError props', () => { - doNotExecute(() => { - useSuspenseInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - // @ts-expect-error TS2345 - placeholderData: 5, - enabled: true, - }) - - useSuspenseInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - // @ts-expect-error TS2345 - enabled: true, - }) - - useSuspenseInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - // @ts-expect-error TS2345 - throwOnError: true, - }) - }) - }) - - it('should not return isPlaceholderData', () => { - doNotExecute(() => { - const query = useSuspenseInfiniteQuery({ - queryKey: ['key'], - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - }) - - // @ts-expect-error TS2339 - void query.isPlaceholderData - }) - }) -}) diff --git a/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx b/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx new file mode 100644 index 0000000000..a6a4b429e4 --- /dev/null +++ b/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx @@ -0,0 +1,140 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import { useInfiniteQuery } from '../useInfiniteQuery' +import { useQuery } from '../useQuery' +import type { InfiniteData } from '@tanstack/query-core' + +describe('pageParam', () => { + it('initialPageParam should define type of param passed to queryFunctionContext', () => { + useInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + }, + initialPageParam: 1, + getNextPageParam: () => undefined, + }) + }) + + it('direction should be passed to queryFn of useInfiniteQuery', () => { + useInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ direction }) => { + expectTypeOf(direction).toEqualTypeOf<'forward' | 'backward'>() + }, + initialPageParam: 1, + getNextPageParam: () => undefined, + }) + }) + + it('there should be no pageParam passed to the queryFn of useQuery', () => { + useQuery({ + queryKey: ['key'], + // @ts-expect-error there should be no pageParam passed to queryFn of useQuery + queryFn: ({ pageParam }) => { + return String(pageParam) + }, + }) + }) + + it('there should be no direction passed to the queryFn of useQuery', () => { + useQuery({ + queryKey: ['key'], + // @ts-expect-error there should be no pageParam passed to queryFn of useQuery + queryFn: ({ direction }) => { + return String(direction) + }, + }) + }) + + it('initialPageParam should define type of param passed to queryFunctionContext for fetchInfiniteQuery', () => { + const queryClient = new QueryClient() + queryClient.fetchInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + }, + initialPageParam: 1, + }) + }) + + it('initialPageParam should define type of param passed to queryFunctionContext for prefetchInfiniteQuery', () => { + const queryClient = new QueryClient() + queryClient.prefetchInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + }, + initialPageParam: 1, + }) + }) +}) +describe('select', () => { + it('should still return paginated data if no select result', () => { + const infiniteQuery = useInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + return pageParam * 5 + }, + initialPageParam: 1, + getNextPageParam: () => undefined, + }) + + // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now + expectTypeOf(infiniteQuery.data).toEqualTypeOf< + InfiniteData | undefined + >() + }) + + it('should be able to transform data to arbitrary result', () => { + const infiniteQuery = useInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + return pageParam * 5 + }, + initialPageParam: 1, + getNextPageParam: () => undefined, + select: (data) => { + expectTypeOf(data).toEqualTypeOf>() + return 'selected' as const + }, + }) + + expectTypeOf(infiniteQuery.data).toEqualTypeOf<'selected' | undefined>() + }) +}) +describe('getNextPageParam / getPreviousPageParam', () => { + it('should get typed params', () => { + const infiniteQuery = useInfiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + return String(pageParam) + }, + initialPageParam: 1, + getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => { + expectTypeOf(lastPage).toEqualTypeOf() + expectTypeOf(allPages).toEqualTypeOf>() + expectTypeOf(lastPageParam).toEqualTypeOf() + expectTypeOf(allPageParams).toEqualTypeOf>() + return undefined + }, + getPreviousPageParam: ( + firstPage, + allPages, + firstPageParam, + allPageParams, + ) => { + expectTypeOf(firstPage).toEqualTypeOf() + expectTypeOf(allPages).toEqualTypeOf>() + expectTypeOf(firstPageParam).toEqualTypeOf() + expectTypeOf(allPageParams).toEqualTypeOf>() + return undefined + }, + }) + + // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now + expectTypeOf(infiniteQuery.data).toEqualTypeOf< + InfiniteData | undefined + >() + }) +}) diff --git a/packages/react-query/src/__tests__/useInfiniteQuery.type.test.tsx b/packages/react-query/src/__tests__/useInfiniteQuery.type.test.tsx deleted file mode 100644 index 42e3ff27cc..0000000000 --- a/packages/react-query/src/__tests__/useInfiniteQuery.type.test.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { describe, it } from 'vitest' -import { QueryClient } from '@tanstack/query-core' -import { useInfiniteQuery } from '../useInfiniteQuery' -import { useQuery } from '../useQuery' -import { doNotExecute } from './utils' -import type { Equal, Expect } from './utils' -import type { InfiniteData } from '@tanstack/query-core' - -describe('pageParam', () => { - it('initialPageParam should define type of param passed to queryFunctionContext', () => { - doNotExecute(() => { - useInfiniteQuery({ - queryKey: ['key'], - queryFn: ({ pageParam }) => { - const result: Expect> = true - return result - }, - initialPageParam: 1, - getNextPageParam: () => undefined, - }) - }) - }) - - it('direction should be passed to queryFn of useInfiniteQuery', () => { - doNotExecute(() => { - useInfiniteQuery({ - queryKey: ['key'], - queryFn: ({ direction }) => { - const result: Expect< - Equal<'forward' | 'backward', typeof direction> - > = true - return result - }, - initialPageParam: 1, - getNextPageParam: () => undefined, - }) - }) - }) - - it('there should be no pageParam passed to the queryFn of useQuery', () => { - doNotExecute(() => { - useQuery({ - queryKey: ['key'], - // @ts-expect-error there should be no pageParam passed to queryFn of useQuery - queryFn: ({ pageParam }) => { - return String(pageParam) - }, - }) - }) - }) - - it('there should be no direction passed to the queryFn of useQuery', () => { - doNotExecute(() => { - useQuery({ - queryKey: ['key'], - // @ts-expect-error there should be no pageParam passed to queryFn of useQuery - queryFn: ({ direction }) => { - return String(direction) - }, - }) - }) - }) - - it('initialPageParam should define type of param passed to queryFunctionContext for fetchInfiniteQuery', () => { - doNotExecute(() => { - const queryClient = new QueryClient() - queryClient.fetchInfiniteQuery({ - queryKey: ['key'], - queryFn: ({ pageParam }) => { - const result: Expect> = true - return result - }, - initialPageParam: 1, - }) - }) - }) - - it('initialPageParam should define type of param passed to queryFunctionContext for prefetchInfiniteQuery', () => { - doNotExecute(() => { - const queryClient = new QueryClient() - queryClient.prefetchInfiniteQuery({ - queryKey: ['key'], - queryFn: ({ pageParam }) => { - const result: Expect> = true - return result - }, - initialPageParam: 1, - }) - }) - }) -}) -describe('select', () => { - it('should still return paginated data if no select result', () => { - doNotExecute(() => { - const infiniteQuery = useInfiniteQuery({ - queryKey: ['key'], - queryFn: ({ pageParam }) => { - return pageParam * 5 - }, - initialPageParam: 1, - getNextPageParam: () => undefined, - }) - - // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now - const result: Expect< - Equal< - InfiniteData | undefined, - (typeof infiniteQuery)['data'] - > - > = true - - return result - }) - }) - - it('should be able to transform data to arbitrary result', () => { - doNotExecute(() => { - const infiniteQuery = useInfiniteQuery({ - queryKey: ['key'], - queryFn: ({ pageParam }) => { - return pageParam * 5 - }, - initialPageParam: 1, - getNextPageParam: () => undefined, - select: (data) => { - const result: Expect< - Equal, typeof data> - > = true - return result - }, - }) - - const result: Expect< - Equal - > = true - return result - }) - }) -}) -describe('getNextPageParam / getPreviousPageParam', () => { - it('should get typed params', () => { - doNotExecute(() => { - const infiniteQuery = useInfiniteQuery({ - queryKey: ['key'], - queryFn: ({ pageParam }) => { - return String(pageParam) - }, - initialPageParam: 1, - getNextPageParam: ( - lastPage, - allPages, - lastPageParam, - allPageParams, - ) => { - doNotExecute(() => { - const lastPageResult: Expect> = true - return lastPageResult - }) - doNotExecute(() => { - const allPagesResult: Expect< - Equal, typeof allPages> - > = true - return allPagesResult - }) - doNotExecute(() => { - const lastPageParamResult: Expect< - Equal - > = true - return lastPageParamResult - }) - doNotExecute(() => { - const allPageParamsResult: Expect< - Equal, typeof allPageParams> - > = true - return allPageParamsResult - }) - - return undefined - }, - getPreviousPageParam: ( - firstPage, - allPages, - firstPageParam, - allPageParams, - ) => { - doNotExecute(() => { - const firstPageResult: Expect> = - true - return firstPageResult - }) - doNotExecute(() => { - const allPagesResult: Expect< - Equal, typeof allPages> - > = true - return allPagesResult - }) - doNotExecute(() => { - const firstPageParamResult: Expect< - Equal - > = true - return firstPageParamResult - }) - doNotExecute(() => { - const allPageParamsResult: Expect< - Equal, typeof allPageParams> - > = true - return allPageParamsResult - }) - - return undefined - }, - }) - - // TODO: Order of generics prevents pageParams to be typed correctly. Using `unknown` for now - const result: Expect< - Equal< - InfiniteData | undefined, - (typeof infiniteQuery)['data'] - > - > = true - return result - }) - }) -}) diff --git a/packages/react-query/src/__tests__/useQueries.test-d.tsx b/packages/react-query/src/__tests__/useQueries.test-d.tsx new file mode 100644 index 0000000000..a1057201cc --- /dev/null +++ b/packages/react-query/src/__tests__/useQueries.test-d.tsx @@ -0,0 +1,126 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { queryOptions } from '../queryOptions' +import { useQueries } from '../useQueries' +import type { UseQueryOptions } from '../types' + +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', + } + + const queryResults = useQueries({ queries: [query1, query2, query3] }) + + const query1Data = queryResults[0].data + const query2Data = queryResults[1].data + const query3Data = queryResults[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: () => { + return { + wow: true, + } + }, + initialData: { + wow: true, + }, + }) + const queryResults = useQueries({ queries: [options] }) + + const data = queryResults[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 useQuery', () => { + 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 + + 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 queryResults = useQueries({ + queries: [ + { + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }, + ], + }) + + const data = queryResults[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 queryResults = useCustomQueries() + const data = queryResults[0].data + + expectTypeOf(data).toEqualTypeOf() + }) + }) +}) diff --git a/packages/react-query/src/__tests__/useQueries.types.test.tsx b/packages/react-query/src/__tests__/useQueries.types.test.tsx deleted file mode 100644 index 6da3e2dd5e..0000000000 --- a/packages/react-query/src/__tests__/useQueries.types.test.tsx +++ /dev/null @@ -1,148 +0,0 @@ -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> = 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 queryResults = useQueries({ queries: [options] }) - - const data = queryResults[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 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> = - 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 queryResults = useQueries({ - queries: [ - { - queryKey: ['key'], - queryFn: () => { - return { - wow: true, - } - }, - initialData: () => undefined as { wow: boolean } | undefined, - }, - ], - }) - - const data = queryResults[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 queryResults = useCustomQueries() - const data = queryResults[0].data - - const result: Expect> = true - return result - }) - }) - }) -}) diff --git a/packages/react-query/src/__tests__/useQuery.test-d.tsx b/packages/react-query/src/__tests__/useQuery.test-d.tsx new file mode 100644 index 0000000000..286dfb89a2 --- /dev/null +++ b/packages/react-query/src/__tests__/useQuery.test-d.tsx @@ -0,0 +1,140 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { useQuery } from '../useQuery' +import { queryOptions } from '../queryOptions' +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 }, + }) + + 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, + }, + }) + const { data } = 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 = 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 }>() + }) + + it('TData should have undefined in the union when initialData is NOT provided', () => { + const { data } = 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 } = useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + 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 } = useQuery({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }) + + if (isSuccess) { + expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() + } + }) + }) + + describe('custom hook', () => { + it('should allow custom hooks using UseQueryOptions', () => { + type Data = string + + const useCustomQuery = ( + options?: Omit, 'queryKey' | 'queryFn'>, + ) => { + return useQuery({ + ...options, + queryKey: ['todos-key'], + queryFn: () => Promise.resolve('data'), + }) + } + + const { data } = useCustomQuery() + + expectTypeOf(data).toEqualTypeOf() + }) + }) + + 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.types.test.tsx b/packages/react-query/src/__tests__/useQuery.types.test.tsx deleted file mode 100644 index 7717885bef..0000000000 --- a/packages/react-query/src/__tests__/useQuery.types.test.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { describe, it } from 'vitest' -import { useQuery } from '../useQuery' -import { queryOptions } from '../queryOptions' -import { doNotExecute } from './utils' -import type { UseQueryOptions } from '../types' -import type { Equal, Expect } from './utils' - -describe('initialData', () => { - describe('Config object overload', () => { - it('TData should always be defined when initialData is provided as an object', () => { - doNotExecute(() => { - const { data } = 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 } = 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 = useQuery({ - ...options, - select: (data) => data > 1, - }) - - const result: Expect< - Equal - > = true - return result - }) - }) - - it('TData should always be defined when initialData is provided as a function which ALWAYS returns the data', () => { - doNotExecute(() => { - const { data } = 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 } = 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 } = 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 } = 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 hook', () => { - it('should allow custom hooks using UseQueryOptions', () => { - doNotExecute(() => { - type Data = string - - const useCustomQuery = ( - options?: Omit, 'queryKey' | 'queryFn'>, - ) => { - return useQuery({ - ...options, - queryKey: ['todos-key'], - queryFn: () => Promise.resolve('data'), - }) - } - - const { data } = 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 - }, - }) - }) - }) - }) -}) diff --git a/packages/react-query/src/__tests__/utils.tsx b/packages/react-query/src/__tests__/utils.tsx index d99b53214f..6c0e79e2e8 100644 --- a/packages/react-query/src/__tests__/utils.tsx +++ b/packages/react-query/src/__tests__/utils.tsx @@ -78,14 +78,6 @@ export function setActTimeout(fn: () => void, ms?: number) { }, ms) } -export type Equal = (() => T extends TTargetA - ? 1 - : 2) extends () => T extends TTargetB ? 1 : 2 - ? true - : false - -export type Expect = T - /** * Assert the parameter is not typed as `any` */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e79a5f6730..bc4efc2daa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,7 +24,7 @@ importers: version: 0.4.4(@types/node@18.19.3)(esbuild@0.19.11)(rollup@4.6.0)(typescript@5.2.2)(vite@5.1.1) '@testing-library/jest-dom': specifier: ^6.4.2 - version: 6.4.2(vitest@1.2.2) + version: 6.4.2(vitest@1.3.1) '@testing-library/react': specifier: ^14.2.1 version: 14.2.1(react-dom@18.2.0)(react@18.2.0) @@ -47,8 +47,8 @@ importers: specifier: ^6.20.0 version: 6.20.0(eslint@8.56.0)(typescript@5.2.2) '@vitest/coverage-istanbul': - specifier: ^1.2.2 - version: 1.2.2(vitest@1.2.2) + specifier: ^1.3.1 + version: 1.3.1(vitest@1.3.1) cpy-cli: specifier: ^5.0.0 version: 5.0.0 @@ -116,8 +116,8 @@ importers: specifier: ^5.1.1 version: 5.1.1(@types/node@18.19.3) vitest: - specifier: ^1.2.2 - version: 1.2.2(@types/node@18.19.3)(jsdom@24.0.0) + specifier: ^1.3.1 + version: 1.3.1(@types/node@18.19.3)(jsdom@24.0.0) examples/angular/basic: dependencies: @@ -8995,7 +8995,7 @@ packages: pretty-format: 27.5.1 dev: true - /@testing-library/jest-dom@6.4.2(vitest@1.2.2): + /@testing-library/jest-dom@6.4.2(vitest@1.3.1): resolution: {integrity: sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} peerDependencies: @@ -9024,7 +9024,7 @@ packages: dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 - vitest: 1.2.2(@types/node@18.19.3)(jsdom@24.0.0) + vitest: 1.3.1(@types/node@18.19.3)(jsdom@24.0.0) /@testing-library/react@14.2.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A==} @@ -9948,10 +9948,10 @@ packages: vue: 3.3.0 dev: true - /@vitest/coverage-istanbul@1.2.2(vitest@1.2.2): - resolution: {integrity: sha512-tJybwO8JT4H9ANz0T0/tJ1M5g3BkuHKYF1w5YO3z9sAiHBdGANrxN9c5lomJx1WSnLzCxQR5xxlJ4TLKbzrR3w==} + /@vitest/coverage-istanbul@1.3.1(vitest@1.3.1): + resolution: {integrity: sha512-aBVgQ2eY9gzrxBJjGKbWgatTU2w1CacEx0n8OMctPzl9836KqoM5X/WigJpjM7wZEtX2N0ZTE5KDGPmVM+o2Wg==} peerDependencies: - vitest: ^1.0.0 + vitest: 1.3.1 dependencies: debug: 4.3.4(supports-color@6.1.0) istanbul-lib-coverage: 3.2.2 @@ -9962,39 +9962,39 @@ packages: magicast: 0.3.3 picocolors: 1.0.0 test-exclude: 6.0.0 - vitest: 1.2.2(@types/node@18.19.3)(jsdom@24.0.0) + vitest: 1.3.1(@types/node@18.19.3)(jsdom@24.0.0) transitivePeerDependencies: - supports-color dev: true - /@vitest/expect@1.2.2: - resolution: {integrity: sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==} + /@vitest/expect@1.3.1: + resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} dependencies: - '@vitest/spy': 1.2.2 - '@vitest/utils': 1.2.2 + '@vitest/spy': 1.3.1 + '@vitest/utils': 1.3.1 chai: 4.3.10 - /@vitest/runner@1.2.2: - resolution: {integrity: sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==} + /@vitest/runner@1.3.1: + resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==} dependencies: - '@vitest/utils': 1.2.2 + '@vitest/utils': 1.3.1 p-limit: 5.0.0 pathe: 1.1.2 - /@vitest/snapshot@1.2.2: - resolution: {integrity: sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==} + /@vitest/snapshot@1.3.1: + resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==} dependencies: magic-string: 0.30.5 pathe: 1.1.2 pretty-format: 29.7.0 - /@vitest/spy@1.2.2: - resolution: {integrity: sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==} + /@vitest/spy@1.3.1: + resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} dependencies: tinyspy: 2.2.0 - /@vitest/utils@1.2.2: - resolution: {integrity: sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==} + /@vitest/utils@1.3.1: + resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 @@ -19427,6 +19427,9 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + /js-tokens@8.0.3: + resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} + /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -19965,6 +19968,8 @@ packages: peerDependenciesMeta: webpack: optional: true + webpack-sources: + optional: true dependencies: webpack: 5.89.0(esbuild@0.19.11) webpack-sources: 3.2.3 @@ -27276,6 +27281,12 @@ packages: resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} dependencies: acorn: 8.11.3 + dev: false + + /strip-literal@2.0.0: + resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} + dependencies: + js-tokens: 8.0.3 /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} @@ -29033,8 +29044,8 @@ packages: - xml2js dev: false - /vite-node@1.2.2(@types/node@18.19.3): - resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==} + /vite-node@1.3.1(@types/node@18.19.3): + resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -29144,7 +29155,7 @@ packages: optional: true dependencies: '@babel/core': 7.23.7 - '@testing-library/jest-dom': 6.4.2(vitest@1.2.2) + '@testing-library/jest-dom': 6.4.2(vitest@1.3.1) '@types/babel__core': 7.20.5 babel-preset-solid: 1.8.6(@babel/core@7.23.7) merge-anything: 5.1.7 @@ -29167,7 +29178,7 @@ packages: optional: true dependencies: '@babel/core': 7.23.7 - '@testing-library/jest-dom': 6.4.2(vitest@1.2.2) + '@testing-library/jest-dom': 6.4.2(vitest@1.3.1) '@types/babel__core': 7.20.5 babel-preset-solid: 1.8.6(@babel/core@7.23.7) merge-anything: 5.1.7 @@ -29309,15 +29320,15 @@ packages: dependencies: vite: 5.1.1(@types/node@18.19.3) - /vitest@1.2.2(@types/node@18.19.3)(jsdom@24.0.0): - resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==} + /vitest@1.3.1(@types/node@18.19.3)(jsdom@24.0.0): + resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': ^1.0.0 - '@vitest/ui': ^1.0.0 + '@vitest/browser': 1.3.1 + '@vitest/ui': 1.3.1 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -29335,13 +29346,12 @@ packages: optional: true dependencies: '@types/node': 18.19.3 - '@vitest/expect': 1.2.2 - '@vitest/runner': 1.2.2 - '@vitest/snapshot': 1.2.2 - '@vitest/spy': 1.2.2 - '@vitest/utils': 1.2.2 + '@vitest/expect': 1.3.1 + '@vitest/runner': 1.3.1 + '@vitest/snapshot': 1.3.1 + '@vitest/spy': 1.3.1 + '@vitest/utils': 1.3.1 acorn-walk: 8.3.2 - cac: 6.7.14 chai: 4.3.10 debug: 4.3.4(supports-color@6.1.0) execa: 8.0.1 @@ -29351,11 +29361,11 @@ packages: pathe: 1.1.2 picocolors: 1.0.0 std-env: 3.7.0 - strip-literal: 1.3.0 + strip-literal: 2.0.0 tinybench: 2.5.1 tinypool: 0.8.2 vite: 5.1.1(@types/node@18.19.3) - vite-node: 1.2.2(@types/node@18.19.3) + vite-node: 1.3.1(@types/node@18.19.3) why-is-node-running: 2.2.2 transitivePeerDependencies: - less