From 1c243ebf68a1a6108e08d500d4cc53e6034d494d Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Thu, 7 Aug 2025 10:36:12 +0900 Subject: [PATCH] test(react-query/queryOptions): add 'useQuery' basic tests with 'queryOptions' --- .../src/__tests__/queryOptions.test.tsx | 396 +++++++++++++++++- 1 file changed, 392 insertions(+), 4 deletions(-) diff --git a/packages/react-query/src/__tests__/queryOptions.test.tsx b/packages/react-query/src/__tests__/queryOptions.test.tsx index 28e539690b..8c246aab95 100644 --- a/packages/react-query/src/__tests__/queryOptions.test.tsx +++ b/packages/react-query/src/__tests__/queryOptions.test.tsx @@ -1,14 +1,402 @@ -import { describe, expect, it } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { queryKey, sleep } from '@tanstack/query-test-utils' +import { QueryClient, useQuery } from '..' import { queryOptions } from '../queryOptions' -import type { UseQueryOptions } from '../types' +import { renderWithClient } from './utils' +import type { UseQueryOptions, UseQueryResult } from '../types' describe('queryOptions', () => { + let queryClient: QueryClient + + beforeEach(() => { + queryClient = new QueryClient() + vi.useFakeTimers() + }) + + afterEach(() => { + vi.useRealTimers() + }) + it('should return the object received as a parameter without any modification.', () => { + const key = queryKey() const object: UseQueryOptions = { - queryKey: ['key'], - queryFn: () => Promise.resolve(5), + queryKey: key, + queryFn: () => sleep(10).then(() => 5), } as const expect(queryOptions(object)).toStrictEqual(object) }) + + describe('useQuery', () => { + it('should allow to set default data value with queryOptions', async () => { + const key = queryKey() + const options = queryOptions({ + queryKey: key, + queryFn: () => sleep(10).then(() => 'test'), + }) + + function Page() { + const { data = 'default' } = useQuery(options) + + return ( +
+

{data}

+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + expect(rendered.getByText('default')).toBeInTheDocument() + await vi.advanceTimersByTimeAsync(11) + expect(rendered.getByText('test')).toBeInTheDocument() + }) + + it('should return the correct states for a successful query with queryOptions', async () => { + const key = queryKey() + const states: Array> = [] + const options = queryOptions({ + queryKey: key, + queryFn: () => sleep(10).then(() => 'test'), + }) + + function Page() { + const state = useQuery(options) + + states.push(state) + + if (state.isPending) { + return pending + } + + if (state.isLoadingError) { + return {state.error.message} + } + + return {state.data} + } + + const rendered = renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(11) + expect(rendered.getByText('test')).toBeInTheDocument() + + expect(states.length).toEqual(2) + expect(states[0]).toEqual({ + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: true, + isPaused: false, + isPending: true, + isInitialLoading: true, + isLoading: true, + isLoadingError: false, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isSuccess: false, + isEnabled: true, + refetch: expect.any(Function), + status: 'pending', + fetchStatus: 'fetching', + promise: expect.any(Promise), + }) + expect(states[1]).toEqual({ + data: 'test', + dataUpdatedAt: expect.any(Number), + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isError: false, + isFetched: true, + isFetchedAfterMount: true, + isFetching: false, + isPaused: false, + isPending: false, + isInitialLoading: false, + isLoading: false, + isLoadingError: false, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isSuccess: true, + isEnabled: true, + refetch: expect.any(Function), + status: 'success', + fetchStatus: 'idle', + promise: expect.any(Promise), + }) + + expect(states[0]!.promise).toEqual(states[1]!.promise) + }) + + it('should return the correct states for an unsuccessful query with queryOptions', async () => { + const key = queryKey() + const states: Array = [] + let index = 0 + const options = queryOptions({ + queryKey: key, + queryFn: () => + sleep(10).then(() => + Promise.reject(new Error(`rejected #${++index}`)), + ), + retry: 1, + retryDelay: 10, + }) + + function Page() { + const state = useQuery(options) + + states.push(state) + + return ( +
+

Status: {state.status}

+
Failure Count: {state.failureCount}
+
Failure Reason: {state.failureReason?.message}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(31) + expect(rendered.getByText('Status: error')).toBeInTheDocument() + + expect(states[0]).toEqual({ + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: true, + isPaused: false, + isPending: true, + isInitialLoading: true, + isLoading: true, + isLoadingError: false, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isSuccess: false, + isEnabled: true, + refetch: expect.any(Function), + status: 'pending', + fetchStatus: 'fetching', + promise: expect.any(Promise), + }) + expect(states[1]).toEqual({ + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 1, + failureReason: new Error('rejected #1'), + errorUpdateCount: 0, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: true, + isPaused: false, + isPending: true, + isInitialLoading: true, + isLoading: true, + isLoadingError: false, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isSuccess: false, + isEnabled: true, + refetch: expect.any(Function), + status: 'pending', + fetchStatus: 'fetching', + promise: expect.any(Promise), + }) + expect(states[2]).toEqual({ + data: undefined, + dataUpdatedAt: 0, + error: new Error('rejected #2'), + errorUpdatedAt: expect.any(Number), + failureCount: 2, + failureReason: new Error('rejected #2'), + errorUpdateCount: 1, + isError: true, + isFetched: true, + isFetchedAfterMount: true, + isFetching: false, + isPaused: false, + isPending: false, + isInitialLoading: false, + isLoading: false, + isLoadingError: true, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isSuccess: false, + isEnabled: true, + refetch: expect.any(Function), + status: 'error', + fetchStatus: 'idle', + promise: expect.any(Promise), + }) + + expect(states[0]!.promise).toEqual(states[1]!.promise) + expect(states[1]!.promise).toEqual(states[2]!.promise) + }) + + it('should be able to select a part of the data with queryOptions', async () => { + const key = queryKey() + const states: Array> = [] + const options = queryOptions({ + queryKey: key, + queryFn: () => sleep(10).then(() => ({ name: 'test' })), + select: (data) => data.name, + }) + + function Page() { + const state = useQuery(options) + + states.push(state) + + return
{state.data}
+ } + + const rendered = renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(11) + expect(rendered.getByText('test')).toBeInTheDocument() + + expect(states.length).toBe(2) + expect(states[0]).toMatchObject({ data: undefined }) + expect(states[1]).toMatchObject({ data: 'test' }) + }) + + it('should support disabled queries via queryOptions', async () => { + const key = queryKey() + const options = queryOptions({ + queryKey: key, + queryFn: () => sleep(10).then(() => 'data'), + enabled: false, + }) + + function Page() { + const query = useQuery(options) + + return ( +
+
FetchStatus: {query.fetchStatus}
+

Data: {query.data || 'no data'}

+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + expect(rendered.getByText('FetchStatus: idle')).toBeInTheDocument() + expect(rendered.getByText('Data: no data')).toBeInTheDocument() + await vi.advanceTimersByTimeAsync(11) + expect(rendered.getByText('FetchStatus: idle')).toBeInTheDocument() + expect(rendered.getByText('Data: no data')).toBeInTheDocument() + }) + + it('should mark query as fetching when using initialData with queryOptions', async () => { + const key = queryKey() + const results: Array> = [] + const options = queryOptions({ + queryKey: key, + queryFn: () => sleep(10).then(() => 'serverData'), + initialData: 'initialData', + }) + + function Page() { + const result = useQuery(options) + + results.push(result) + + return
data: {result.data}
+ } + + const rendered = renderWithClient(queryClient, ) + + expect(rendered.getByText('data: initialData')).toBeInTheDocument() + await vi.advanceTimersByTimeAsync(11) + expect(rendered.getByText('data: serverData')).toBeInTheDocument() + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + data: 'initialData', + isFetching: true, + }) + expect(results[1]).toMatchObject({ + data: 'serverData', + isFetching: false, + }) + }) + + it('should start with status pending, fetchStatus idle if enabled is false with queryOptions', async () => { + const key1 = queryKey() + const key2 = queryKey() + const options1 = queryOptions({ + queryKey: key1, + queryFn: () => sleep(10).then(() => 'data1'), + enabled: false, + }) + const options2 = queryOptions({ + queryKey: key2, + queryFn: () => sleep(10).then(() => 'data2'), + }) + + function Page() { + const first = useQuery(options1) + const second = useQuery(options2) + + return ( +
+
+ First Status: {first.status}, {first.fetchStatus} +
+
+ Second Status: {second.status}, {second.fetchStatus} +
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + expect( + rendered.getByText('First Status: pending, idle'), + ).toBeInTheDocument() + expect( + rendered.getByText('Second Status: pending, fetching'), + ).toBeInTheDocument() + await vi.advanceTimersByTimeAsync(11) + expect( + rendered.getByText('First Status: pending, idle'), + ).toBeInTheDocument() + expect( + rendered.getByText('Second Status: success, idle'), + ).toBeInTheDocument() + }) + }) })