Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vue-query): add support for infiniteQueryOptions #7257

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
150 changes: 150 additions & 0 deletions 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<InfiniteData<string, number>, 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<typeof data, InfiniteData<string, unknown> | 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<string>>
> = 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<string>>
> = 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<string>>
> = 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<typeof data, InfiniteData<string, unknown> | 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<string, unknown> | undefined
>()
return prev
})

const result: Expect<
Equal<typeof data, InfiniteData<string, unknown> | undefined>
> = true

return result
})
})
})
17 changes: 17 additions & 0 deletions packages/vue-query/src/__tests__/queryClient.test.ts
Expand Up @@ -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')
Expand Down Expand Up @@ -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,
Expand Down
32 changes: 32 additions & 0 deletions 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')
Expand Down Expand Up @@ -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'],
Expand Down
5 changes: 5 additions & 0 deletions packages/vue-query/src/index.ts
Expand Up @@ -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'
Expand Down
94 changes: 94 additions & 0 deletions 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<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> = UseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryFnData,
TQueryKey,
TPageParam
> & {
initialData?: undefined
}

type NonUndefinedGuard<T> = T extends undefined ? never : T

export type DefinedInitialDataInfiniteOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
> = UseInfiniteQueryOptions<
TQueryFnData,
TError,
TData,
TQueryFnData,
TQueryKey,
TPageParam
> & {
initialData:
| NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>
| (() => NonUndefinedGuard<InfiniteData<TQueryFnData, TPageParam>>)
}

export function infiniteQueryOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: UndefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): UndefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
> & {
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>>
}

export function infiniteQueryOptions<
TQueryFnData,
TError = DefaultError,
TData = InfiniteData<TQueryFnData>,
TQueryKey extends QueryKey = QueryKey,
TPageParam = unknown,
>(
options: DefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
>,
): DefinedInitialDataInfiniteOptions<
TQueryFnData,
TError,
TData,
TQueryKey,
TPageParam
> & {
queryKey: DataTag<TQueryKey, InfiniteData<TQueryFnData>>
}

export function infiniteQueryOptions(options: unknown) {
return options
}