diff --git a/packages/preact-query/src/__tests__/useMutation.test.tsx b/packages/preact-query/src/__tests__/useMutation.test.tsx index 48ed686fb6a..fb64bb2adae 100644 --- a/packages/preact-query/src/__tests__/useMutation.test.tsx +++ b/packages/preact-query/src/__tests__/useMutation.test.tsx @@ -1898,11 +1898,11 @@ describe('useMutation', () => { function Page() { const [result, setResult] = useState('idle') - const createUserMutation = useMutation({ + const { mutateAsync: createUserAsync } = useMutation({ mutationFn: (name: string) => sleep(10).then(() => ({ id: '1', name })), }) - const updateProfileMutation = useMutation({ + const { mutateAsync: updateProfileAsync } = useMutation({ mutationFn: (userId: string) => sleep(10).then(() => `profile updated for ${userId}`), }) @@ -1911,8 +1911,8 @@ describe('useMutation', () => {
))} @@ -2156,7 +2156,7 @@ describe('useMutation', () => { const [message, setMessage] = useState('') - const deleteMutation = useMutation({ + const { mutate } = useMutation({ mutationFn: (item: string) => sleep(10).then(() => { throw new Error(`Failed to delete ${item}`) @@ -2181,7 +2181,7 @@ describe('useMutation', () => { return (
{items.map((item) => ( - ))} @@ -2207,4 +2207,144 @@ describe('useMutation', () => { expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() expect(rendered.getByText('message: rollback')).toBeInTheDocument() }) + + it('should be able to run multiple mutateAsync calls in parallel with Promise.all', async () => { + function Page() { + const [result, setResult] = useState('idle') + + const { mutateAsync } = useMutation({ + mutationFn: (file: string) => sleep(10).then(() => `uploaded: ${file}`), + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /upload all/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText( + 'result: uploaded: file1, uploaded: file2, uploaded: file3', + ), + ).toBeInTheDocument() + }) + + it('should handle Promise.all rejection when one parallel mutateAsync call fails', async () => { + function Page() { + const [result, setResult] = useState('idle') + + const { mutateAsync } = useMutation({ + mutationFn: async (file: string) => { + await sleep(10) + if (file === 'file2') { + throw new Error('upload failed') + } + return `uploaded: ${file}` + }, + retry: false, + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /upload all/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('result: error: upload failed'), + ).toBeInTheDocument() + }) + + it('should handle partial failure in parallel mutateAsync calls with Promise.allSettled', async () => { + function Page() { + const [result, setResult] = useState('idle') + + const { mutateAsync } = useMutation({ + mutationFn: async (file: string) => { + await sleep(10) + if (file === 'file2') { + throw new Error('upload failed') + } + return `uploaded: ${file}` + }, + retry: false, + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /upload all/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText( + 'result: uploaded: file1, error: upload failed, uploaded: file3', + ), + ).toBeInTheDocument() + }) }) diff --git a/packages/react-query/src/__tests__/useMutation.test.tsx b/packages/react-query/src/__tests__/useMutation.test.tsx index 8fbf481e980..41a17429640 100644 --- a/packages/react-query/src/__tests__/useMutation.test.tsx +++ b/packages/react-query/src/__tests__/useMutation.test.tsx @@ -1897,11 +1897,11 @@ describe('useMutation', () => { function Page() { const [result, setResult] = React.useState('idle') - const createUserMutation = useMutation({ + const { mutateAsync: createUserAsync } = useMutation({ mutationFn: (name: string) => sleep(10).then(() => ({ id: '1', name })), }) - const updateProfileMutation = useMutation({ + const { mutateAsync: updateProfileAsync } = useMutation({ mutationFn: (userId: string) => sleep(10).then(() => `profile updated for ${userId}`), }) @@ -1910,8 +1910,8 @@ describe('useMutation', () => {
))} @@ -2155,7 +2155,7 @@ describe('useMutation', () => { const [message, setMessage] = React.useState('') - const deleteMutation = useMutation({ + const { mutate } = useMutation({ mutationFn: (item: string) => sleep(10).then(() => { throw new Error(`Failed to delete ${item}`) @@ -2180,7 +2180,7 @@ describe('useMutation', () => { return (
{items.map((item) => ( - ))} @@ -2206,4 +2206,144 @@ describe('useMutation', () => { expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() expect(rendered.getByText('message: rollback')).toBeInTheDocument() }) + + it('should be able to run multiple mutateAsync calls in parallel with Promise.all', async () => { + function Page() { + const [result, setResult] = React.useState('idle') + + const { mutateAsync } = useMutation({ + mutationFn: (file: string) => sleep(10).then(() => `uploaded: ${file}`), + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /upload all/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText( + 'result: uploaded: file1, uploaded: file2, uploaded: file3', + ), + ).toBeInTheDocument() + }) + + it('should handle Promise.all rejection when one parallel mutateAsync call fails', async () => { + function Page() { + const [result, setResult] = React.useState('idle') + + const { mutateAsync } = useMutation({ + mutationFn: async (file: string) => { + await sleep(10) + if (file === 'file2') { + throw new Error('upload failed') + } + return `uploaded: ${file}` + }, + retry: false, + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /upload all/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('result: error: upload failed'), + ).toBeInTheDocument() + }) + + it('should handle partial failure in parallel mutateAsync calls with Promise.allSettled', async () => { + function Page() { + const [result, setResult] = React.useState('idle') + + const { mutateAsync } = useMutation({ + mutationFn: async (file: string) => { + await sleep(10) + if (file === 'file2') { + throw new Error('upload failed') + } + return `uploaded: ${file}` + }, + retry: false, + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /upload all/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText( + 'result: uploaded: file1, error: upload failed, uploaded: file3', + ), + ).toBeInTheDocument() + }) })