From c73eb3098178e5984ed239854ce90d666c16d7c0 Mon Sep 17 00:00:00 2001 From: Davide Segullo Date: Sat, 8 Jun 2024 12:38:41 -0500 Subject: [PATCH] feat(vue-query): add support for infiniteQueryOptions (#7257) * feat(vue-query): :sparkles: add support for infiniteQueryOptions * test(vue-query): :white_check_mark: add infiniteQueryOptions types tests * test(vue-query): :white_check_mark: add test for infiniteQueryOptions * test: :white_check_mark: add more infiniteQueryOptions test * fix: :white_check_mark: fix failing test --- .../infiniteQueryOptions.types.test.ts | 150 ++++++++++++++++++ .../src/__tests__/queryClient.test.ts | 17 ++ .../src/__tests__/useInfiniteQuery.test.ts | 32 ++++ packages/vue-query/src/index.ts | 5 + .../vue-query/src/infiniteQueryOptions.ts | 94 +++++++++++ 5 files changed, 298 insertions(+) create mode 100644 packages/vue-query/src/__tests__/infiniteQueryOptions.types.test.ts create mode 100644 packages/vue-query/src/infiniteQueryOptions.ts diff --git a/packages/vue-query/src/__tests__/infiniteQueryOptions.types.test.ts b/packages/vue-query/src/__tests__/infiniteQueryOptions.types.test.ts new file mode 100644 index 0000000000..761e76e308 --- /dev/null +++ b/packages/vue-query/src/__tests__/infiniteQueryOptions.types.test.ts @@ -0,0 +1,150 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import { reactive } from 'vue-demi' +import { infiniteQueryOptions } from '../infiniteQueryOptions' +import { useInfiniteQuery } from '../useInfiniteQuery' +import { type Equal, type Expect, doNotExecute } from './test-utils' +import type { InfiniteData, dataTagSymbol } from '@tanstack/query-core' + +describe('infiniteQueryOptions', () => { + it('should not allow excess properties', () => { + doNotExecute(() => + 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(() => + 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 } = reactive(useInfiniteQuery(options)) + + const result: Expect< + Equal | undefined> + > = 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) => { + expectTypeOf(prev).toEqualTypeOf< + InfiniteData | undefined + >() + return prev + }) + + const result: Expect< + Equal | undefined> + > = true + + return result + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/queryClient.test.ts b/packages/vue-query/src/__tests__/queryClient.test.ts index 6d9684a6ed..f875a59d54 100644 --- a/packages/vue-query/src/__tests__/queryClient.test.ts +++ b/packages/vue-query/src/__tests__/queryClient.test.ts @@ -2,6 +2,7 @@ import { describe, expect, test, vi } from 'vitest' import { ref } from 'vue-demi' import { QueryClient as QueryClientOrigin } from '@tanstack/query-core' import { QueryClient } from '../queryClient' +import { infiniteQueryOptions } from '../infiniteQueryOptions' import { flushPromises } from './test-utils' vi.mock('@tanstack/query-core') @@ -264,6 +265,22 @@ describe('QueryCache', () => { initialPageParam: 0, }) + expect(QueryClientOrigin.prototype.fetchInfiniteQuery).toBeCalledWith({ + initialPageParam: 0, + queryKey: queryKeyUnref, + }) + }) + test('should properly unwrap parameter using infiniteQueryOptions with unref', async () => { + const queryClient = new QueryClient() + + const options = infiniteQueryOptions({ + queryKey: queryKeyUnref, + initialPageParam: 0, + getNextPageParam: () => 12, + }) + + queryClient.fetchInfiniteQuery(options) + expect(QueryClientOrigin.prototype.fetchInfiniteQuery).toBeCalledWith({ initialPageParam: 0, queryKey: queryKeyUnref, diff --git a/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts b/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts index 1c42b38d2c..5cb6e0cba2 100644 --- a/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts +++ b/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test, vi } from 'vitest' import { useInfiniteQuery } from '../useInfiniteQuery' +import { infiniteQueryOptions } from '../infiniteQueryOptions' import { flushPromises, infiniteFetcher } from './test-utils' vi.mock('../useQueryClient') @@ -28,6 +29,37 @@ describe('useQuery', () => { await flushPromises() + expect(data.value).toStrictEqual({ + pageParams: [0, 12], + pages: ['data on page 0', 'data on page 12'], + }) + expect(status.value).toStrictEqual('success') + }) + test('should properly execute infinite query using infiniteQueryOptions', async () => { + const options = infiniteQueryOptions({ + queryKey: ['infiniteQueryOptions'], + queryFn: infiniteFetcher, + initialPageParam: 0, + getNextPageParam: () => 12, + }) + + const { data, fetchNextPage, status } = useInfiniteQuery(options) + + expect(data.value).toStrictEqual(undefined) + expect(status.value).toStrictEqual('pending') + + await flushPromises() + + expect(data.value).toStrictEqual({ + pageParams: [0], + pages: ['data on page 0'], + }) + expect(status.value).toStrictEqual('success') + + fetchNextPage() + + await flushPromises() + expect(data.value).toStrictEqual({ pageParams: [0, 12], pages: ['data on page 0', 'data on page 12'], diff --git a/packages/vue-query/src/index.ts b/packages/vue-query/src/index.ts index 846cabfaf4..c3ea3e98e0 100644 --- a/packages/vue-query/src/index.ts +++ b/packages/vue-query/src/index.ts @@ -6,6 +6,11 @@ export { VueQueryPlugin } from './vueQueryPlugin' export { QueryClient } from './queryClient' export { QueryCache } from './queryCache' export { queryOptions } from './queryOptions' +export { infiniteQueryOptions } from './infiniteQueryOptions' +export type { + DefinedInitialDataInfiniteOptions, + UndefinedInitialDataInfiniteOptions, +} from './infiniteQueryOptions' export { MutationCache } from './mutationCache' export { useQuery } from './useQuery' export { useQueries } from './useQueries' diff --git a/packages/vue-query/src/infiniteQueryOptions.ts b/packages/vue-query/src/infiniteQueryOptions.ts new file mode 100644 index 0000000000..f457718728 --- /dev/null +++ b/packages/vue-query/src/infiniteQueryOptions.ts @@ -0,0 +1,94 @@ +import type { DataTag } from '@tanstack/query-core' +import type { InfiniteData } from '@tanstack/query-core' +import type { UseInfiniteQueryOptions } from './useInfiniteQuery' +import type { DefaultError, QueryKey } from '@tanstack/query-core' + +export type UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey, + TPageParam +> & { + initialData?: undefined +} + +type NonUndefinedGuard = T extends undefined ? never : T + +export type DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey, + TPageParam +> & { + initialData: + | NonUndefinedGuard> + | (() => NonUndefinedGuard>) +} + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): UndefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> & { + queryKey: DataTag> +} + +export function infiniteQueryOptions< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): DefinedInitialDataInfiniteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> & { + queryKey: DataTag> +} + +export function infiniteQueryOptions(options: unknown) { + return options +}