UNPKG

@tanstack/vue-query

Version:

Hooks for managing, caching and syncing asynchronous and remote data in Vue

365 lines (294 loc) 9.45 kB
import { describe, expect, test, vi } from 'vitest' import { computed, getCurrentInstance, onScopeDispose, reactive, ref, } from 'vue-demi' import { QueryObserver } from '@tanstack/query-core' import { useQuery } from '../useQuery' import { useBaseQuery } from '../useBaseQuery' import { flushPromises, getSimpleFetcherWithReturnData, rejectFetcher, simpleFetcher, } from './test-utils' import type { Mock, MockedFunction } from 'vitest' vi.mock('../useQueryClient') vi.mock('../useBaseQuery') describe('useQuery', () => { test('should properly execute query', () => { useQuery({ queryKey: ['key0'], queryFn: simpleFetcher, staleTime: 1000 }) expect(useBaseQuery).toBeCalledWith( QueryObserver, { queryKey: ['key0'], queryFn: simpleFetcher, staleTime: 1000, }, undefined, ) }) test('should return pending status initially', () => { const query = useQuery({ queryKey: ['key1'], queryFn: simpleFetcher }) expect(query).toMatchObject({ status: { value: 'pending' }, isPending: { value: true }, isFetching: { value: true }, isStale: { value: true }, }) }) test('should resolve to success and update reactive state: useQuery(key, dataFn)', async () => { const query = useQuery({ queryKey: ['key2'], queryFn: getSimpleFetcherWithReturnData('result2'), }) await flushPromises() expect(query).toMatchObject({ status: { value: 'success' }, data: { value: 'result2' }, isPending: { value: false }, isFetching: { value: false }, isFetched: { value: true }, isSuccess: { value: true }, }) }) test('should resolve to success and update reactive state: useQuery(optionsObj)', async () => { const query = useQuery({ queryKey: ['key31'], queryFn: getSimpleFetcherWithReturnData('result31'), enabled: true, }) await flushPromises() expect(query).toMatchObject({ status: { value: 'success' }, data: { value: 'result31' }, isPending: { value: false }, isFetching: { value: false }, isFetched: { value: true }, isSuccess: { value: true }, }) }) test('should resolve to success and update reactive state: useQuery(key, optionsObj)', async () => { const query = useQuery({ queryKey: ['key32'], queryFn: getSimpleFetcherWithReturnData('result32'), enabled: true, }) await flushPromises() expect(query).toMatchObject({ status: { value: 'success' }, data: { value: 'result32' }, isPending: { value: false }, isFetching: { value: false }, isFetched: { value: true }, isSuccess: { value: true }, }) }) test('should reject and update reactive state', async () => { const query = useQuery({ queryKey: ['key3'], queryFn: rejectFetcher, }) await flushPromises() expect(query).toMatchObject({ status: { value: 'error' }, data: { value: undefined }, error: { value: { message: 'Some error' } }, isPending: { value: false }, isFetching: { value: false }, isFetched: { value: true }, isError: { value: true }, failureCount: { value: 1 }, failureReason: { value: { message: 'Some error' } }, }) }) test('should update query on reactive (Ref) key change', async () => { const secondKeyRef = ref('key7') const query = useQuery({ queryKey: ['key6', secondKeyRef], queryFn: simpleFetcher, }) await flushPromises() expect(query).toMatchObject({ status: { value: 'success' }, }) secondKeyRef.value = 'key8' await flushPromises() expect(query).toMatchObject({ status: { value: 'pending' }, data: { value: undefined }, }) await flushPromises() expect(query).toMatchObject({ status: { value: 'success' }, }) }) test("should update query when an option is passed as Ref and it's changed", async () => { const enabled = ref(false) const query = useQuery({ queryKey: ['key9'], queryFn: simpleFetcher, enabled, }) await flushPromises() expect(query).toMatchObject({ fetchStatus: { value: 'idle' }, data: { value: undefined }, }) enabled.value = true await flushPromises() expect(query).toMatchObject({ fetchStatus: { value: 'fetching' }, data: { value: undefined }, }) await flushPromises() expect(query).toMatchObject({ status: { value: 'success' }, }) }) test('should properly execute dependant queries', async () => { const { data } = useQuery({ queryKey: ['dependant1'], queryFn: simpleFetcher, }) const enabled = computed(() => !!data.value) const dependentQueryFn = vi.fn().mockImplementation(simpleFetcher) const { fetchStatus, status } = useQuery( reactive({ queryKey: ['dependant2'], queryFn: dependentQueryFn, enabled, }), ) expect(data.value).toStrictEqual(undefined) expect(fetchStatus.value).toStrictEqual('idle') expect(dependentQueryFn).not.toHaveBeenCalled() await flushPromises() expect(data.value).toStrictEqual('Some data') expect(fetchStatus.value).toStrictEqual('fetching') await flushPromises() expect(fetchStatus.value).toStrictEqual('idle') expect(status.value).toStrictEqual('success') expect(dependentQueryFn).toHaveBeenCalledTimes(1) expect(dependentQueryFn).toHaveBeenCalledWith( expect.objectContaining({ queryKey: ['dependant2'] }), ) }) test('should stop listening to changes on onScopeDispose', async () => { const onScopeDisposeMock = onScopeDispose as MockedFunction< typeof onScopeDispose > onScopeDisposeMock.mockImplementationOnce((fn) => fn()) const { status } = useQuery({ queryKey: ['onScopeDispose'], queryFn: simpleFetcher, }) expect(status.value).toStrictEqual('pending') await flushPromises() expect(status.value).toStrictEqual('pending') await flushPromises() expect(status.value).toStrictEqual('pending') }) test('should use the current value for the queryKey when refetch is called', async () => { const fetchFn = vi.fn() const keyRef = ref('key11') const query = useQuery({ queryKey: ['key10', keyRef], queryFn: fetchFn, enabled: false, }) expect(fetchFn).not.toHaveBeenCalled() await query.refetch() expect(fetchFn).toHaveBeenCalledTimes(1) expect(fetchFn).toHaveBeenCalledWith( expect.objectContaining({ queryKey: ['key10', 'key11'], }), ) keyRef.value = 'key12' await query.refetch() expect(fetchFn).toHaveBeenCalledTimes(2) expect(fetchFn).toHaveBeenCalledWith( expect.objectContaining({ queryKey: ['key10', 'key12'], }), ) }) test('should be `enabled` to accept getter function', async () => { const fetchFn = vi.fn() const checked = ref(false) useQuery({ queryKey: ['enabled'], queryFn: fetchFn, enabled: () => checked.value, }) expect(fetchFn).not.toHaveBeenCalled() checked.value = true await flushPromises() expect(fetchFn).toHaveBeenCalled() }) describe('throwOnError', () => { test('should evaluate throwOnError when query is expected to throw', async () => { const boundaryFn = vi.fn() useQuery({ queryKey: ['key0'], queryFn: rejectFetcher, retry: false, throwOnError: boundaryFn, }) await flushPromises() expect(boundaryFn).toHaveBeenCalledTimes(1) expect(boundaryFn).toHaveBeenCalledWith( Error('Some error'), expect.objectContaining({ state: expect.objectContaining({ status: 'error' }), }), ) }) }) describe('suspense', () => { test('should return a Promise', () => { const getCurrentInstanceSpy = getCurrentInstance as Mock getCurrentInstanceSpy.mockImplementation(() => ({ suspense: {} })) const query = useQuery({ queryKey: ['suspense'], queryFn: simpleFetcher }) const result = query.suspense() expect(result).toBeInstanceOf(Promise) }) test('should resolve after being enabled', () => { const getCurrentInstanceSpy = getCurrentInstance as Mock getCurrentInstanceSpy.mockImplementation(() => ({ suspense: {} })) let afterTimeout = false const isEnabeld = ref(false) const query = useQuery({ queryKey: ['suspense'], queryFn: simpleFetcher, enabled: isEnabeld, }) setTimeout(() => { afterTimeout = true isEnabeld.value = true }, 200) return query.suspense().then(() => { expect(afterTimeout).toBe(true) }) }) test('should resolve immidiately when stale without refetching', () => { const getCurrentInstanceSpy = getCurrentInstance as Mock getCurrentInstanceSpy.mockImplementation(() => ({ suspense: {} })) const fetcherSpy = vi.fn(() => simpleFetcher()) // let afterTimeout = false; const query = useQuery({ queryKey: ['suspense'], queryFn: simpleFetcher, staleTime: 10000, initialData: 'foo', }) return query.suspense().then(() => { expect(fetcherSpy).toHaveBeenCalledTimes(0) }) }) }) })