From 38ba62093e1560a76f0ebb68d9b8a62a6fa2524a Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Mon, 30 Mar 2026 02:16:43 +0900 Subject: [PATCH 1/3] test({react,preact}-query/useSuspenseQueries): inline test helpers, remove shared 'queryClient', and use 'beforeEach'/'afterEach' for setup and teardown --- .../src/__tests__/useSuspenseQueries.test.tsx | 216 +++++++++++------ .../src/__tests__/useSuspenseQueries.test.tsx | 219 +++++++++++++----- 2 files changed, 306 insertions(+), 129 deletions(-) diff --git a/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx b/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx index 8fa4072c69..c2150c2052 100644 --- a/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx +++ b/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx @@ -1,6 +1,5 @@ import { queryKey, sleep } from '@tanstack/query-test-utils' import { act, fireEvent, render } from '@testing-library/preact' -import type { FunctionalComponent } from 'preact' import { Suspense, startTransition, useTransition } from 'preact/compat' import { useEffect, useRef, useState } from 'preact/hooks' import { @@ -24,33 +23,18 @@ import type { UseSuspenseQueryOptions } from '..' import { ErrorBoundary } from './ErrorBoundary' import { renderWithClient } from './utils' -type NumberQueryOptions = UseSuspenseQueryOptions - -const QUERY_DURATION = 1000 - -const createQuery: (id: number) => NumberQueryOptions = (id) => ({ - queryKey: [id], - queryFn: () => sleep(QUERY_DURATION).then(() => id), -}) -const resolveQueries = async () => { - await vi.advanceTimersByTimeAsync(QUERY_DURATION) -} - -const queryClient = new QueryClient() - describe('useSuspenseQueries', () => { + let queryClient: QueryClient const onSuspend = vi.fn() const onQueriesResolution = vi.fn() - beforeAll(() => { + beforeEach(() => { vi.useFakeTimers() - }) - - afterAll(() => { - vi.useRealTimers() + queryClient = new QueryClient() }) afterEach(() => { + vi.useRealTimers() queryClient.clear() onSuspend.mockClear() onQueriesResolution.mockClear() @@ -64,89 +48,183 @@ describe('useSuspenseQueries', () => { return
loading
} - const withSuspenseWrapper = ( - Component: FunctionalComponent, - ) => { - function SuspendedComponent(props: T) { - return ( - }> - - + it('should suspend on mount', () => { + function Page() { + const queriesResults = useSuspenseQueries( + { + queries: [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })), + combine: (results) => results.map((r) => r.data), + }, + queryClient, ) - } - - return SuspendedComponent - } - - function QueriesContainer({ - queries, - }: { - queries: Array - }) { - const queriesResults = useSuspenseQueries( - { queries, combine: (results) => results.map((r) => r.data) }, - queryClient, - ) - - useEffect(() => { - onQueriesResolution(queriesResults) - }, [queriesResults]) - return null - } + useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) - const TestComponent = withSuspenseWrapper(QueriesContainer) + return null + } - it('should suspend on mount', () => { - render() + render( + }> + + , + ) expect(onSuspend).toHaveBeenCalledOnce() }) it('should resolve queries', async () => { - render() + function Page() { + const queriesResults = useSuspenseQueries( + { + queries: [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })), + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) + + useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) - await act(resolveQueries) + return null + } + + render( + }> + + , + ) + + await act(() => vi.advanceTimersByTimeAsync(1000)) expect(onQueriesResolution).toHaveBeenCalledTimes(1) expect(onQueriesResolution).toHaveBeenLastCalledWith([1, 2]) }) it('should not suspend on mount if query has been already fetched', () => { - const query = createQuery(1) + const key = [1] as const + const queryFn = () => sleep(1000).then(() => 1) + + queryClient.setQueryData(key, queryFn) - queryClient.setQueryData(query.queryKey, query.queryFn) + function Page() { + const queriesResults = useSuspenseQueries( + { + queries: [{ queryKey: key, queryFn }], + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) - render() + useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) + + return null + } + + render( + }> + + , + ) expect(onSuspend).not.toHaveBeenCalled() }) it('should not break suspense when queries change without resolving', async () => { - const initQueries = [1, 2].map(createQuery) - const nextQueries = [3, 4, 5, 6].map(createQuery) + const initQueries = [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + const nextQueries = [3, 4, 5, 6].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + + function Page({ queries }: { queries: Array> }) { + const queriesResults = useSuspenseQueries( + { + queries, + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) - const { rerender } = render() + useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) - rerender() + return null + } - await act(resolveQueries) + const { rerender } = render( + }> + + , + ) + + rerender( + }> + + , + ) + + await vi.advanceTimersByTimeAsync(1000) expect(onSuspend).toHaveBeenCalled() // the test for onQueriesResolution is React-specific and not applicable to Preact }) it('should suspend only once per queries change', async () => { - const initQueries = [1, 2].map(createQuery) - const nextQueries = [3, 4, 5, 6].map(createQuery) + const initQueries = [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + const nextQueries = [3, 4, 5, 6].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + + function Page({ queries }: { queries: Array> }) { + const queriesResults = useSuspenseQueries( + { + queries, + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) - const { rerender } = render() + useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) - await act(resolveQueries) + return null + } - rerender() + const { rerender } = render( + }> + + , + ) + + await act(() => vi.advanceTimersByTimeAsync(1000)) + + rerender( + }> + + , + ) - await act(resolveQueries) + await act(() => vi.advanceTimersByTimeAsync(1000)) expect(onSuspend).toHaveBeenCalledTimes(2) expect(onQueriesResolution).toHaveBeenCalledTimes(2) @@ -263,12 +341,16 @@ describe('useSuspenseQueries', () => { }) describe('useSuspenseQueries 2', () => { + let queryClient: QueryClient + beforeEach(() => { vi.useFakeTimers() + queryClient = new QueryClient() }) afterEach(() => { vi.useRealTimers() + queryClient.clear() }) it('should suspend all queries in parallel', async () => { diff --git a/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx b/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx index 4ebd32ea90..dc7f3a55de 100644 --- a/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx +++ b/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx @@ -21,31 +21,18 @@ import { import { renderWithClient } from './utils' import type { UseSuspenseQueryOptions } from '..' -type NumberQueryOptions = UseSuspenseQueryOptions - -const QUERY_DURATION = 1000 - -const createQuery: (id: number) => NumberQueryOptions = (id) => ({ - queryKey: [id], - queryFn: () => sleep(QUERY_DURATION).then(() => id), -}) -const resolveQueries = () => vi.advanceTimersByTimeAsync(QUERY_DURATION) - -const queryClient = new QueryClient() - describe('useSuspenseQueries', () => { + let queryClient: QueryClient const onSuspend = vi.fn() const onQueriesResolution = vi.fn() - beforeAll(() => { + beforeEach(() => { vi.useFakeTimers() - }) - - afterAll(() => { - vi.useRealTimers() + queryClient = new QueryClient() }) afterEach(() => { + vi.useRealTimers() queryClient.clear() onSuspend.mockClear() onQueriesResolution.mockClear() @@ -59,71 +46,141 @@ describe('useSuspenseQueries', () => { return
loading
} - const withSuspenseWrapper = (Component: React.FC) => { - function SuspendedComponent(props: T) { - return ( - }> - - + it('should suspend on mount', () => { + function Page() { + const queriesResults = useSuspenseQueries( + { + queries: [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })), + combine: (results) => results.map((r) => r.data), + }, + queryClient, ) - } - - return SuspendedComponent - } - - function QueriesContainer({ - queries, - }: { - queries: Array - }) { - const queriesResults = useSuspenseQueries( - { queries, combine: (results) => results.map((r) => r.data) }, - queryClient, - ) - - React.useEffect(() => { - onQueriesResolution(queriesResults) - }, [queriesResults]) - return null - } + React.useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) - const TestComponent = withSuspenseWrapper(QueriesContainer) + return null + } - it('should suspend on mount', () => { - render() + render( + }> + + , + ) expect(onSuspend).toHaveBeenCalledOnce() }) it('should resolve queries', async () => { - render() + function Page() { + const queriesResults = useSuspenseQueries( + { + queries: [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })), + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) + + React.useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) + + return null + } + + render( + }> + + , + ) - await act(resolveQueries) + await act(() => vi.advanceTimersByTimeAsync(1000)) expect(onQueriesResolution).toHaveBeenCalledTimes(1) expect(onQueriesResolution).toHaveBeenLastCalledWith([1, 2]) }) it('should not suspend on mount if query has been already fetched', () => { - const query = createQuery(1) + const key = [1] as const + const queryFn = () => sleep(1000).then(() => 1) + + queryClient.setQueryData(key, queryFn) + + function Page() { + const queriesResults = useSuspenseQueries( + { + queries: [{ queryKey: key, queryFn }], + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) - queryClient.setQueryData(query.queryKey, query.queryFn) + React.useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) - render() + return null + } + + render( + }> + + , + ) expect(onSuspend).not.toHaveBeenCalled() }) it('should not break suspense when queries change without resolving', async () => { - const initQueries = [1, 2].map(createQuery) - const nextQueries = [3, 4, 5, 6].map(createQuery) + const initQueries = [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + const nextQueries = [3, 4, 5, 6].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + + function Page({ + queries, + }: { + queries: Array> + }) { + const queriesResults = useSuspenseQueries( + { + queries, + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) - const { rerender } = render() + React.useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) - rerender() + return null + } - await act(resolveQueries) + const { rerender } = render( + }> + + , + ) + + rerender( + }> + + , + ) + + await act(() => vi.advanceTimersByTimeAsync(1000)) expect(onSuspend).toHaveBeenCalledTimes(1) expect(onQueriesResolution).toHaveBeenCalledTimes(1) @@ -131,16 +188,50 @@ describe('useSuspenseQueries', () => { }) it('should suspend only once per queries change', async () => { - const initQueries = [1, 2].map(createQuery) - const nextQueries = [3, 4, 5, 6].map(createQuery) + const initQueries = [1, 2].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + const nextQueries = [3, 4, 5, 6].map((id) => ({ + queryKey: [id], + queryFn: () => sleep(1000).then(() => id), + })) + + function Page({ + queries, + }: { + queries: Array> + }) { + const queriesResults = useSuspenseQueries( + { + queries, + combine: (results) => results.map((r) => r.data), + }, + queryClient, + ) + + React.useEffect(() => { + onQueriesResolution(queriesResults) + }, [queriesResults]) + + return null + } - const { rerender } = render() + const { rerender } = render( + }> + + , + ) - await act(resolveQueries) + await act(() => vi.advanceTimersByTimeAsync(1000)) - rerender() + rerender( + }> + + , + ) - await act(resolveQueries) + await act(() => vi.advanceTimersByTimeAsync(1000)) expect(onSuspend).toHaveBeenCalledTimes(2) expect(onQueriesResolution).toHaveBeenCalledTimes(2) @@ -258,12 +349,16 @@ describe('useSuspenseQueries', () => { }) describe('useSuspenseQueries 2', () => { + let queryClient: QueryClient + beforeEach(() => { vi.useFakeTimers() + queryClient = new QueryClient() }) afterEach(() => { vi.useRealTimers() + queryClient.clear() }) it('should suspend all queries in parallel', async () => { From 93e908fd6c43fb8019b10bd9eb45cb0917e5d14d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 17:18:33 +0000 Subject: [PATCH 2/3] ci: apply automated fixes --- .../src/__tests__/useSuspenseQueries.test.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx b/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx index c2150c2052..ceedb4f84b 100644 --- a/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx +++ b/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx @@ -150,7 +150,11 @@ describe('useSuspenseQueries', () => { queryFn: () => sleep(1000).then(() => id), })) - function Page({ queries }: { queries: Array> }) { + function Page({ + queries, + }: { + queries: Array> + }) { const queriesResults = useSuspenseQueries( { queries, @@ -194,7 +198,11 @@ describe('useSuspenseQueries', () => { queryFn: () => sleep(1000).then(() => id), })) - function Page({ queries }: { queries: Array> }) { + function Page({ + queries, + }: { + queries: Array> + }) { const queriesResults = useSuspenseQueries( { queries, From cacc7583d995bbc719360dd96f2bb2c306da5ce7 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Mon, 30 Mar 2026 02:27:27 +0900 Subject: [PATCH 3/3] test({react,preact}-query/useSuspenseQueries): replace '[1] as const' with 'queryKey()' util and fix 'act' callback type in preact-query --- .../src/__tests__/useSuspenseQueries.test.tsx | 18 +++++++++++++----- .../src/__tests__/useSuspenseQueries.test.tsx | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx b/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx index ceedb4f84b..ac56ae947b 100644 --- a/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx +++ b/packages/preact-query/src/__tests__/useSuspenseQueries.test.tsx @@ -103,14 +103,16 @@ describe('useSuspenseQueries', () => { , ) - await act(() => vi.advanceTimersByTimeAsync(1000)) + await act(async () => { + await vi.advanceTimersByTimeAsync(1000) + }) expect(onQueriesResolution).toHaveBeenCalledTimes(1) expect(onQueriesResolution).toHaveBeenLastCalledWith([1, 2]) }) it('should not suspend on mount if query has been already fetched', () => { - const key = [1] as const + const key = queryKey() const queryFn = () => sleep(1000).then(() => 1) queryClient.setQueryData(key, queryFn) @@ -182,7 +184,9 @@ describe('useSuspenseQueries', () => { , ) - await vi.advanceTimersByTimeAsync(1000) + await act(async () => { + await vi.advanceTimersByTimeAsync(1000) + }) expect(onSuspend).toHaveBeenCalled() // the test for onQueriesResolution is React-specific and not applicable to Preact @@ -224,7 +228,9 @@ describe('useSuspenseQueries', () => { , ) - await act(() => vi.advanceTimersByTimeAsync(1000)) + await act(async () => { + await vi.advanceTimersByTimeAsync(1000) + }) rerender( }> @@ -232,7 +238,9 @@ describe('useSuspenseQueries', () => { , ) - await act(() => vi.advanceTimersByTimeAsync(1000)) + await act(async () => { + await vi.advanceTimersByTimeAsync(1000) + }) expect(onSuspend).toHaveBeenCalledTimes(2) expect(onQueriesResolution).toHaveBeenCalledTimes(2) diff --git a/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx b/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx index dc7f3a55de..46561056bf 100644 --- a/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx +++ b/packages/react-query/src/__tests__/useSuspenseQueries.test.tsx @@ -108,7 +108,7 @@ describe('useSuspenseQueries', () => { }) it('should not suspend on mount if query has been already fetched', () => { - const key = [1] as const + const key = queryKey() const queryFn = () => sleep(1000).then(() => 1) queryClient.setQueryData(key, queryFn)