diff --git a/packages/preact-query/src/__tests__/useMutation.test-d.tsx b/packages/preact-query/src/__tests__/useMutation.test-d.tsx new file mode 100644 index 00000000000..7b7a5806058 --- /dev/null +++ b/packages/preact-query/src/__tests__/useMutation.test-d.tsx @@ -0,0 +1,138 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import { useMutation } from '../useMutation' +import type { DefaultError } from '@tanstack/query-core' +import type { UseMutationResult } from '../types' + +describe('useMutation', () => { + it('should infer TData from mutationFn return type', () => { + const mutation = useMutation({ + mutationFn: () => Promise.resolve('data'), + }) + + expectTypeOf(mutation.data).toEqualTypeOf() + expectTypeOf(mutation.error).toEqualTypeOf() + }) + + it('should infer TVariables from mutationFn parameter', () => { + const mutation = useMutation({ + mutationFn: (vars: { id: string }) => Promise.resolve(vars.id), + }) + + expectTypeOf(mutation.mutate).toBeCallableWith({ id: '1' }) + expectTypeOf(mutation.data).toEqualTypeOf() + }) + + it('should infer TOnMutateResult from onMutate return type', () => { + useMutation({ + mutationFn: () => Promise.resolve('data'), + onMutate: () => { + return { token: 'abc' } + }, + onSuccess: (_data, _variables, onMutateResult) => { + expectTypeOf(onMutateResult).toEqualTypeOf<{ token: string }>() + }, + onError: (_error, _variables, onMutateResult) => { + expectTypeOf(onMutateResult).toEqualTypeOf< + { token: string } | undefined + >() + }, + }) + }) + + it('should allow explicit generic types', () => { + const mutation = useMutation({ + mutationFn: (vars) => { + expectTypeOf(vars).toEqualTypeOf<{ id: number }>() + return Promise.resolve('result') + }, + }) + + expectTypeOf(mutation.data).toEqualTypeOf() + expectTypeOf(mutation.error).toEqualTypeOf() + }) + + it('should return correct UseMutationResult type', () => { + const mutation = useMutation({ + mutationFn: () => Promise.resolve(42), + }) + + expectTypeOf(mutation).toEqualTypeOf< + UseMutationResult + >() + }) + + it('should type mutateAsync with correct return type', () => { + const mutation = useMutation({ + mutationFn: (id: string) => Promise.resolve(id.length), + }) + + expectTypeOf(mutation.mutateAsync).toBeCallableWith('test') + expectTypeOf(mutation.mutateAsync('test')).toEqualTypeOf>() + }) + + it('should default TVariables to void when mutationFn has no parameters', () => { + const mutation = useMutation({ + mutationFn: () => Promise.resolve('data'), + }) + + expectTypeOf(mutation.mutate).toBeCallableWith() + }) + + it('should infer custom TError type', () => { + class CustomError extends Error { + code: number + constructor(code: number) { + super() + this.code = code + } + } + + const mutation = useMutation({ + mutationFn: () => Promise.resolve('data'), + }) + + expectTypeOf(mutation.error).toEqualTypeOf() + expectTypeOf(mutation.data).toEqualTypeOf() + }) + + it('should infer types for onSettled callback', () => { + useMutation({ + mutationFn: () => Promise.resolve(42), + onSettled: (data, error, _variables, _onMutateResult) => { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + }, + }) + }) + + it('should infer custom TError in onError callback', () => { + class CustomError extends Error { + code: number + constructor(code: number) { + super() + this.code = code + } + } + + useMutation({ + mutationFn: () => Promise.resolve('data'), + onError: (error) => { + expectTypeOf(error).toEqualTypeOf() + }, + }) + }) + + it('should accept queryClient as second argument', () => { + const queryClient = new QueryClient() + + const mutation = useMutation( + { + mutationFn: () => Promise.resolve('data'), + }, + queryClient, + ) + + expectTypeOf(mutation.data).toEqualTypeOf() + }) +}) diff --git a/packages/preact-query/src/__tests__/useMutation.test.tsx b/packages/preact-query/src/__tests__/useMutation.test.tsx index f060c496f08..48ed686fb6a 100644 --- a/packages/preact-query/src/__tests__/useMutation.test.tsx +++ b/packages/preact-query/src/__tests__/useMutation.test.tsx @@ -100,6 +100,332 @@ describe('useMutation', () => { expect(queryByRole('heading')).toBeNull() }) + it('should call mutate callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onSuccess', 'mutate.onSettled']) + }) + + it('should call mutateAsync callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSuccess: () => { + callbacks.push('mutateAsync.onSuccess') + }, + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'mutateAsync.onSuccess', + 'mutateAsync.onSettled', + ]) + }) + + it('should call mutate error callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onError', 'mutate.onSettled']) + }) + + it('should call mutateAsync error callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + }, + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + } catch {} + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onError', 'mutateAsync.onSettled']) + }) + + it('should call only mutate onSuccess when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onSuccess']) + }) + + it('should call only mutate onError when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onError']) + }) + + it('should call only mutate onSettled when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onSettled']) + }) + + it('should call only mutateAsync onSuccess when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSuccess: () => { + callbacks.push('mutateAsync.onSuccess') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onSuccess']) + }) + + it('should call only mutateAsync onError when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + }, + }) + } catch {} + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onError']) + }) + + it('should call only mutateAsync onSettled when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onSettled']) + }) + it('should be able to call `onSuccess` and `onSettled` after each successful mutate', async () => { let count = 0 const onSuccessMock = vi.fn() @@ -264,6 +590,215 @@ describe('useMutation', () => { ) }) + it('should be able to call `onSuccess` callback after successful mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSuccess: () => { + callbacks.push('useMutation.onSuccess') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onSuccess', 'mutate.onSuccess']) + }) + + it('should be able to call `onError` callback after failed mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + onError: () => { + callbacks.push('useMutation.onError') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onError', 'mutate.onError']) + }) + + it('should be able to call `onSettled` callback after mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSettled: () => { + callbacks.push('useMutation.onSettled') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onSettled', 'mutate.onSettled']) + }) + + it('should be able to call `onSuccess` callback after successful mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSuccess: () => { + callbacks.push('useMutation.onSuccess') + }, + }) + + useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSuccess: () => { + callbacks.push('mutateAsync.onSuccess') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onSuccess', + 'mutateAsync.onSuccess', + ]) + }) + + it('should be able to call `onError` callback after failed mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + onError: () => { + callbacks.push('useMutation.onError') + }, + }) + + useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + }, + }) + } catch {} + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onError', 'mutateAsync.onError']) + }) + + it('should be able to call `onSettled` callback after mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSettled: () => { + callbacks.push('useMutation.onSettled') + }, + }) + + useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onSettled', + 'mutateAsync.onSettled', + ]) + }) + it('should be able to override the useMutation success callbacks', async () => { const callbacks: Array = [] @@ -301,67 +836,158 @@ describe('useMutation', () => { return null } - renderWithClient(queryClient, ) + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onSuccess', + 'useMutation.onSettled', + 'mutateAsync.onSuccess', + 'mutateAsync.onSettled', + 'mutateAsync.result:todo', + ]) + }) + + it('should be able to override the error callbacks when using mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => Promise.reject(new Error('oops')), + onError: () => { + callbacks.push('useMutation.onError') + return Promise.resolve() + }, + onSettled: () => { + callbacks.push('useMutation.onSettled') + return Promise.resolve() + }, + }) + + useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + return Promise.resolve() + }, + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + return Promise.resolve() + }, + }) + } catch (error) { + callbacks.push(`mutateAsync.error:${(error as Error).message}`) + } + }, 10) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onError', + 'useMutation.onSettled', + 'mutateAsync.onError', + 'mutateAsync.onSettled', + 'mutateAsync.error:oops', + ]) + }) + + it('should be able to override the error callbacks when using mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => Promise.reject(new Error('oops'))), + onError: () => { + callbacks.push('useMutation.onError') + }, + onSettled: () => { + callbacks.push('useMutation.onSettled') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) await vi.advanceTimersByTimeAsync(10) expect(callbacks).toEqual([ - 'useMutation.onSuccess', + 'useMutation.onError', 'useMutation.onSettled', - 'mutateAsync.onSuccess', - 'mutateAsync.onSettled', - 'mutateAsync.result:todo', + 'mutate.onError', + 'mutate.onSettled', ]) }) - it('should be able to override the error callbacks when using mutateAsync', async () => { + it('should be able to override the settled callbacks when using mutate', async () => { const callbacks: Array = [] function Page() { - const { mutateAsync } = useMutation({ - mutationFn: async (_text: string) => Promise.reject(new Error('oops')), - onError: () => { - callbacks.push('useMutation.onError') - return Promise.resolve() + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSuccess: () => { + callbacks.push('useMutation.onSuccess') }, onSettled: () => { callbacks.push('useMutation.onSettled') - return Promise.resolve() }, }) - useEffect(() => { - setActTimeout(async () => { - try { - await mutateAsync('todo', { - onError: () => { - callbacks.push('mutateAsync.onError') - return Promise.resolve() + return ( + + ) } - renderWithClient(queryClient, ) + const rendered = renderWithClient(queryClient, ) + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) await vi.advanceTimersByTimeAsync(10) expect(callbacks).toEqual([ - 'useMutation.onError', + 'useMutation.onSuccess', 'useMutation.onSettled', - 'mutateAsync.onError', - 'mutateAsync.onSettled', - 'mutateAsync.error:oops', + 'mutate.onSuccess', + 'mutate.onSettled', ]) }) @@ -783,6 +1409,83 @@ describe('useMutation', () => { consoleMock.mockRestore() }) + it('should not throw an error when throwOnError is set to false', async () => { + function Page() { + const { mutate, error } = useMutation({ + mutationFn: () => + sleep(10).then(() => { + throw new Error('Expected mock error') + }), + throwOnError: false, + }) + + return ( +
+ +
error: {error?.message ?? 'null'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('error: Expected mock error')).toBeInTheDocument() + }) + + it('should not throw an error when throwOnError is a function that returns false', async () => { + function Page() { + const { mutate, error } = useMutation({ + mutationFn: () => + sleep(10).then(() => { + throw new Error('Expected mock error') + }), + throwOnError: () => false, + }) + + return ( +
+ +
error: {error?.message ?? 'null'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('error: Expected mock error')).toBeInTheDocument() + }) + + it('should not throw an error when throwOnError is not set', async () => { + function Page() { + const { mutate, error } = useMutation({ + mutationFn: () => + sleep(10).then(() => { + throw new Error('Expected mock error') + }), + }) + + return ( +
+ +
error: {error?.message ?? 'null'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('error: Expected mock error')).toBeInTheDocument() + }) + it('should pass meta to mutation', async () => { const errorMock = vi.fn() const successMock = vi.fn() @@ -1190,4 +1893,318 @@ describe('useMutation', () => { rendered.getByText('data: custom client, status: success'), ).toBeInTheDocument() }) + + it('should be able to chain mutateAsync calls sequentially', async () => { + function Page() { + const [result, setResult] = useState('idle') + + const createUserMutation = useMutation({ + mutationFn: (name: string) => sleep(10).then(() => ({ id: '1', name })), + }) + + const updateProfileMutation = useMutation({ + mutationFn: (userId: string) => + sleep(10).then(() => `profile updated for ${userId}`), + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /chain/i })) + await vi.advanceTimersByTimeAsync(10) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('result: profile updated for 1'), + ).toBeInTheDocument() + }) + + it('should handle error in chained mutateAsync calls', async () => { + function Page() { + const [result, setResult] = useState('idle') + + const createUserMutation = useMutation({ + mutationFn: (_name: string) => + sleep(10).then<{ id: string }>(() => { + throw new Error('create failed') + }), + }) + + const updateProfileMutation = useMutation({ + mutationFn: (userId: string) => + sleep(10).then(() => `profile updated for ${userId}`), + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /chain/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('result: error: create failed'), + ).toBeInTheDocument() + }) + + it('should handle conditional logic based on mutate success or failure', async () => { + function Page() { + const [message, setMessage] = useState('idle') + + const submitMutation = useMutation({ + mutationFn: async (shouldFail: boolean) => { + await sleep(10) + if (shouldFail) { + throw new Error('submission failed') + } + return 'submitted successfully' + }, + retry: false, + }) + + return ( +
+ + +
message: {message}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /^submit$/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('message: success: submitted successfully'), + ).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /submit fail/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('message: error: submission failed'), + ).toBeInTheDocument() + }) + + it('should handle conditional error with retry using mutate', async () => { + let attempt = 0 + + function Page() { + const [message, setMessage] = useState('idle') + + const submitMutation = useMutation({ + mutationFn: async () => { + await sleep(10) + attempt++ + if (attempt < 2) { + throw new Error('temporary failure') + } + return 'success' + }, + retry: false, + }) + + return ( +
+ +
message: {message}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /submit/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('message: failed, retrying...'), + ).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /submit/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('message: result: success')).toBeInTheDocument() + }) + + it('should support optimistic update on success', async () => { + function Page() { + const [items, setItems] = useState>([ + 'item1', + 'item2', + 'item3', + ]) + + const [successMessage, setSuccessMessage] = useState('') + + const deleteMutation = useMutation({ + mutationFn: (item: string) => sleep(10).then(() => item), + onMutate: (item) => { + const previousItems = [...items] + setItems((prev) => prev.filter((i) => i !== item)) + return { previousItems } + }, + onSuccess: (deletedItem) => { + setSuccessMessage(`deleted: ${deletedItem}`) + }, + onError: (_error, _item, context) => { + if (context?.previousItems) { + setItems(context.previousItems) + } + }, + }) + + return ( +
+ {items.map((item) => ( + + ))} +
items: {items.join(', ')}
+
success: {successMessage || 'none'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() + expect(rendered.getByText('success: none')).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /delete item2/i })) + + // optimistic update: item2 removed immediately + expect(rendered.getByText('items: item1, item3')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + + // success: item2 stays removed and onSuccess called + expect(rendered.getByText('items: item1, item3')).toBeInTheDocument() + expect(rendered.getByText('success: deleted: item2')).toBeInTheDocument() + }) + + it('should support optimistic update and rollback on error', async () => { + function Page() { + const [items, setItems] = useState>([ + 'item1', + 'item2', + 'item3', + ]) + + const [message, setMessage] = useState('') + + const deleteMutation = useMutation({ + mutationFn: (item: string) => + sleep(10).then(() => { + throw new Error(`Failed to delete ${item}`) + }), + onMutate: (item) => { + const previousItems = [...items] + setItems((prev) => prev.filter((i) => i !== item)) + return { previousItems } + }, + onSuccess: (deletedItem) => { + setMessage(`deleted: ${deletedItem}`) + }, + onError: (_error, _item, context) => { + setMessage('rollback') + if (context?.previousItems) { + setItems(context.previousItems) + } + }, + retry: false, + }) + + return ( +
+ {items.map((item) => ( + + ))} +
items: {items.join(', ')}
+
message: {message || 'none'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() + expect(rendered.getByText('message: none')).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /delete item2/i })) + + // optimistic update: item2 removed immediately + expect(rendered.getByText('items: item1, item3')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + + // rollback: item2 restored after error, onSuccess not called + expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() + expect(rendered.getByText('message: rollback')).toBeInTheDocument() + }) }) diff --git a/packages/react-query/src/__tests__/useMutation.test-d.tsx b/packages/react-query/src/__tests__/useMutation.test-d.tsx new file mode 100644 index 00000000000..7b7a5806058 --- /dev/null +++ b/packages/react-query/src/__tests__/useMutation.test-d.tsx @@ -0,0 +1,138 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import { useMutation } from '../useMutation' +import type { DefaultError } from '@tanstack/query-core' +import type { UseMutationResult } from '../types' + +describe('useMutation', () => { + it('should infer TData from mutationFn return type', () => { + const mutation = useMutation({ + mutationFn: () => Promise.resolve('data'), + }) + + expectTypeOf(mutation.data).toEqualTypeOf() + expectTypeOf(mutation.error).toEqualTypeOf() + }) + + it('should infer TVariables from mutationFn parameter', () => { + const mutation = useMutation({ + mutationFn: (vars: { id: string }) => Promise.resolve(vars.id), + }) + + expectTypeOf(mutation.mutate).toBeCallableWith({ id: '1' }) + expectTypeOf(mutation.data).toEqualTypeOf() + }) + + it('should infer TOnMutateResult from onMutate return type', () => { + useMutation({ + mutationFn: () => Promise.resolve('data'), + onMutate: () => { + return { token: 'abc' } + }, + onSuccess: (_data, _variables, onMutateResult) => { + expectTypeOf(onMutateResult).toEqualTypeOf<{ token: string }>() + }, + onError: (_error, _variables, onMutateResult) => { + expectTypeOf(onMutateResult).toEqualTypeOf< + { token: string } | undefined + >() + }, + }) + }) + + it('should allow explicit generic types', () => { + const mutation = useMutation({ + mutationFn: (vars) => { + expectTypeOf(vars).toEqualTypeOf<{ id: number }>() + return Promise.resolve('result') + }, + }) + + expectTypeOf(mutation.data).toEqualTypeOf() + expectTypeOf(mutation.error).toEqualTypeOf() + }) + + it('should return correct UseMutationResult type', () => { + const mutation = useMutation({ + mutationFn: () => Promise.resolve(42), + }) + + expectTypeOf(mutation).toEqualTypeOf< + UseMutationResult + >() + }) + + it('should type mutateAsync with correct return type', () => { + const mutation = useMutation({ + mutationFn: (id: string) => Promise.resolve(id.length), + }) + + expectTypeOf(mutation.mutateAsync).toBeCallableWith('test') + expectTypeOf(mutation.mutateAsync('test')).toEqualTypeOf>() + }) + + it('should default TVariables to void when mutationFn has no parameters', () => { + const mutation = useMutation({ + mutationFn: () => Promise.resolve('data'), + }) + + expectTypeOf(mutation.mutate).toBeCallableWith() + }) + + it('should infer custom TError type', () => { + class CustomError extends Error { + code: number + constructor(code: number) { + super() + this.code = code + } + } + + const mutation = useMutation({ + mutationFn: () => Promise.resolve('data'), + }) + + expectTypeOf(mutation.error).toEqualTypeOf() + expectTypeOf(mutation.data).toEqualTypeOf() + }) + + it('should infer types for onSettled callback', () => { + useMutation({ + mutationFn: () => Promise.resolve(42), + onSettled: (data, error, _variables, _onMutateResult) => { + expectTypeOf(data).toEqualTypeOf() + expectTypeOf(error).toEqualTypeOf() + }, + }) + }) + + it('should infer custom TError in onError callback', () => { + class CustomError extends Error { + code: number + constructor(code: number) { + super() + this.code = code + } + } + + useMutation({ + mutationFn: () => Promise.resolve('data'), + onError: (error) => { + expectTypeOf(error).toEqualTypeOf() + }, + }) + }) + + it('should accept queryClient as second argument', () => { + const queryClient = new QueryClient() + + const mutation = useMutation( + { + mutationFn: () => Promise.resolve('data'), + }, + queryClient, + ) + + expectTypeOf(mutation.data).toEqualTypeOf() + }) +}) diff --git a/packages/react-query/src/__tests__/useMutation.test.tsx b/packages/react-query/src/__tests__/useMutation.test.tsx index 821a4571afb..8fbf481e980 100644 --- a/packages/react-query/src/__tests__/useMutation.test.tsx +++ b/packages/react-query/src/__tests__/useMutation.test.tsx @@ -99,6 +99,332 @@ describe('useMutation', () => { expect(queryByRole('heading')).toBeNull() }) + it('should call mutate callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onSuccess', 'mutate.onSettled']) + }) + + it('should call mutateAsync callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + React.useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSuccess: () => { + callbacks.push('mutateAsync.onSuccess') + }, + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'mutateAsync.onSuccess', + 'mutateAsync.onSettled', + ]) + }) + + it('should call mutate error callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onError', 'mutate.onSettled']) + }) + + it('should call mutateAsync error callbacks when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + React.useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + }, + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + } catch {} + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onError', 'mutateAsync.onSettled']) + }) + + it('should call only mutate onSuccess when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onSuccess']) + }) + + it('should call only mutate onError when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onError']) + }) + + it('should call only mutate onSettled when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutate.onSettled']) + }) + + it('should call only mutateAsync onSuccess when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + React.useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSuccess: () => { + callbacks.push('mutateAsync.onSuccess') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onSuccess']) + }) + + it('should call only mutateAsync onError when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + }) + + React.useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + }, + }) + } catch {} + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onError']) + }) + + it('should call only mutateAsync onSettled when useMutation has no callbacks', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + }) + + React.useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['mutateAsync.onSettled']) + }) + it('should be able to call `onSuccess` and `onSettled` after each successful mutate', async () => { let count = 0 const onSuccessMock = vi.fn() @@ -263,6 +589,215 @@ describe('useMutation', () => { ) }) + it('should be able to call `onSuccess` callback after successful mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSuccess: () => { + callbacks.push('useMutation.onSuccess') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onSuccess', 'mutate.onSuccess']) + }) + + it('should be able to call `onError` callback after failed mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + onError: () => { + callbacks.push('useMutation.onError') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onError', 'mutate.onError']) + }) + + it('should be able to call `onSettled` callback after mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSettled: () => { + callbacks.push('useMutation.onSettled') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onSettled', 'mutate.onSettled']) + }) + + it('should be able to call `onSuccess` callback after successful mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSuccess: () => { + callbacks.push('useMutation.onSuccess') + }, + }) + + React.useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSuccess: () => { + callbacks.push('mutateAsync.onSuccess') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onSuccess', + 'mutateAsync.onSuccess', + ]) + }) + + it('should be able to call `onError` callback after failed mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => { + throw new Error('oops') + }), + onError: () => { + callbacks.push('useMutation.onError') + }, + }) + + React.useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + }, + }) + } catch {} + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual(['useMutation.onError', 'mutateAsync.onError']) + }) + + it('should be able to call `onSettled` callback after mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSettled: () => { + callbacks.push('useMutation.onSettled') + }, + }) + + React.useEffect(() => { + setActTimeout(async () => { + await mutateAsync('todo', { + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + }, + }) + }, 0) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onSettled', + 'mutateAsync.onSettled', + ]) + }) + it('should be able to override the useMutation success callbacks', async () => { const callbacks: Array = [] @@ -300,67 +835,158 @@ describe('useMutation', () => { return null } - renderWithClient(queryClient, ) + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onSuccess', + 'useMutation.onSettled', + 'mutateAsync.onSuccess', + 'mutateAsync.onSettled', + 'mutateAsync.result:todo', + ]) + }) + + it('should be able to override the error callbacks when using mutateAsync', async () => { + const callbacks: Array = [] + + function Page() { + const { mutateAsync } = useMutation({ + mutationFn: async (_text: string) => Promise.reject(new Error('oops')), + onError: () => { + callbacks.push('useMutation.onError') + return Promise.resolve() + }, + onSettled: () => { + callbacks.push('useMutation.onSettled') + return Promise.resolve() + }, + }) + + React.useEffect(() => { + setActTimeout(async () => { + try { + await mutateAsync('todo', { + onError: () => { + callbacks.push('mutateAsync.onError') + return Promise.resolve() + }, + onSettled: () => { + callbacks.push('mutateAsync.onSettled') + return Promise.resolve() + }, + }) + } catch (error) { + callbacks.push(`mutateAsync.error:${(error as Error).message}`) + } + }, 10) + }, [mutateAsync]) + + return null + } + + renderWithClient(queryClient, ) + + await vi.advanceTimersByTimeAsync(10) + + expect(callbacks).toEqual([ + 'useMutation.onError', + 'useMutation.onSettled', + 'mutateAsync.onError', + 'mutateAsync.onSettled', + 'mutateAsync.error:oops', + ]) + }) + + it('should be able to override the error callbacks when using mutate', async () => { + const callbacks: Array = [] + + function Page() { + const { mutate } = useMutation({ + mutationFn: async (_text: string) => + sleep(10).then(() => Promise.reject(new Error('oops'))), + onError: () => { + callbacks.push('useMutation.onError') + }, + onSettled: () => { + callbacks.push('useMutation.onSettled') + }, + }) + + return ( + + ) + } + + const rendered = renderWithClient(queryClient, ) + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) await vi.advanceTimersByTimeAsync(10) expect(callbacks).toEqual([ - 'useMutation.onSuccess', + 'useMutation.onError', 'useMutation.onSettled', - 'mutateAsync.onSuccess', - 'mutateAsync.onSettled', - 'mutateAsync.result:todo', + 'mutate.onError', + 'mutate.onSettled', ]) }) - it('should be able to override the error callbacks when using mutateAsync', async () => { + it('should be able to override the settled callbacks when using mutate', async () => { const callbacks: Array = [] function Page() { - const { mutateAsync } = useMutation({ - mutationFn: async (_text: string) => Promise.reject(new Error('oops')), - onError: () => { - callbacks.push('useMutation.onError') - return Promise.resolve() + const { mutate } = useMutation({ + mutationFn: (text: string) => sleep(10).then(() => text), + onSuccess: () => { + callbacks.push('useMutation.onSuccess') }, onSettled: () => { callbacks.push('useMutation.onSettled') - return Promise.resolve() }, }) - React.useEffect(() => { - setActTimeout(async () => { - try { - await mutateAsync('todo', { - onError: () => { - callbacks.push('mutateAsync.onError') - return Promise.resolve() + return ( + + ) } - renderWithClient(queryClient, ) + const rendered = renderWithClient(queryClient, ) + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) await vi.advanceTimersByTimeAsync(10) expect(callbacks).toEqual([ - 'useMutation.onError', + 'useMutation.onSuccess', 'useMutation.onSettled', - 'mutateAsync.onError', - 'mutateAsync.onSettled', - 'mutateAsync.error:oops', + 'mutate.onSuccess', + 'mutate.onSettled', ]) }) @@ -782,6 +1408,83 @@ describe('useMutation', () => { consoleMock.mockRestore() }) + it('should not throw an error when throwOnError is set to false', async () => { + function Page() { + const { mutate, error } = useMutation({ + mutationFn: () => + sleep(10).then(() => { + throw new Error('Expected mock error') + }), + throwOnError: false, + }) + + return ( +
+ +
error: {error?.message ?? 'null'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('error: Expected mock error')).toBeInTheDocument() + }) + + it('should not throw an error when throwOnError is a function that returns false', async () => { + function Page() { + const { mutate, error } = useMutation({ + mutationFn: () => + sleep(10).then(() => { + throw new Error('Expected mock error') + }), + throwOnError: () => false, + }) + + return ( +
+ +
error: {error?.message ?? 'null'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('error: Expected mock error')).toBeInTheDocument() + }) + + it('should not throw an error when throwOnError is not set', async () => { + function Page() { + const { mutate, error } = useMutation({ + mutationFn: () => + sleep(10).then(() => { + throw new Error('Expected mock error') + }), + }) + + return ( +
+ +
error: {error?.message ?? 'null'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('error: Expected mock error')).toBeInTheDocument() + }) + it('should pass meta to mutation', async () => { const errorMock = vi.fn() const successMock = vi.fn() @@ -1189,4 +1892,318 @@ describe('useMutation', () => { rendered.getByText('data: custom client, status: success'), ).toBeInTheDocument() }) + + it('should be able to chain mutateAsync calls sequentially', async () => { + function Page() { + const [result, setResult] = React.useState('idle') + + const createUserMutation = useMutation({ + mutationFn: (name: string) => sleep(10).then(() => ({ id: '1', name })), + }) + + const updateProfileMutation = useMutation({ + mutationFn: (userId: string) => + sleep(10).then(() => `profile updated for ${userId}`), + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /chain/i })) + await vi.advanceTimersByTimeAsync(10) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('result: profile updated for 1'), + ).toBeInTheDocument() + }) + + it('should handle error in chained mutateAsync calls', async () => { + function Page() { + const [result, setResult] = React.useState('idle') + + const createUserMutation = useMutation({ + mutationFn: (_name: string) => + sleep(10).then<{ id: string }>(() => { + throw new Error('create failed') + }), + }) + + const updateProfileMutation = useMutation({ + mutationFn: (userId: string) => + sleep(10).then(() => `profile updated for ${userId}`), + }) + + return ( +
+ +
result: {result}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /chain/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('result: error: create failed'), + ).toBeInTheDocument() + }) + + it('should handle conditional logic based on mutate success or failure', async () => { + function Page() { + const [message, setMessage] = React.useState('idle') + + const submitMutation = useMutation({ + mutationFn: async (shouldFail: boolean) => { + await sleep(10) + if (shouldFail) { + throw new Error('submission failed') + } + return 'submitted successfully' + }, + retry: false, + }) + + return ( +
+ + +
message: {message}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /^submit$/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('message: success: submitted successfully'), + ).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /submit fail/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('message: error: submission failed'), + ).toBeInTheDocument() + }) + + it('should handle conditional error with retry using mutate', async () => { + let attempt = 0 + + function Page() { + const [message, setMessage] = React.useState('idle') + + const submitMutation = useMutation({ + mutationFn: async () => { + await sleep(10) + attempt++ + if (attempt < 2) { + throw new Error('temporary failure') + } + return 'success' + }, + retry: false, + }) + + return ( +
+ +
message: {message}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + fireEvent.click(rendered.getByRole('button', { name: /submit/i })) + await vi.advanceTimersByTimeAsync(11) + + expect( + rendered.getByText('message: failed, retrying...'), + ).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /submit/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('message: result: success')).toBeInTheDocument() + }) + + it('should support optimistic update on success', async () => { + function Page() { + const [items, setItems] = React.useState>([ + 'item1', + 'item2', + 'item3', + ]) + + const [successMessage, setSuccessMessage] = React.useState('') + + const deleteMutation = useMutation({ + mutationFn: (item: string) => sleep(10).then(() => item), + onMutate: (item) => { + const previousItems = [...items] + setItems((prev) => prev.filter((i) => i !== item)) + return { previousItems } + }, + onSuccess: (deletedItem) => { + setSuccessMessage(`deleted: ${deletedItem}`) + }, + onError: (_error, _item, context) => { + if (context?.previousItems) { + setItems(context.previousItems) + } + }, + }) + + return ( +
+ {items.map((item) => ( + + ))} +
items: {items.join(', ')}
+
success: {successMessage || 'none'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() + expect(rendered.getByText('success: none')).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /delete item2/i })) + + // optimistic update: item2 removed immediately + expect(rendered.getByText('items: item1, item3')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + + // success: item2 stays removed and onSuccess called + expect(rendered.getByText('items: item1, item3')).toBeInTheDocument() + expect(rendered.getByText('success: deleted: item2')).toBeInTheDocument() + }) + + it('should support optimistic update and rollback on error', async () => { + function Page() { + const [items, setItems] = React.useState>([ + 'item1', + 'item2', + 'item3', + ]) + + const [message, setMessage] = React.useState('') + + const deleteMutation = useMutation({ + mutationFn: (item: string) => + sleep(10).then(() => { + throw new Error(`Failed to delete ${item}`) + }), + onMutate: (item) => { + const previousItems = [...items] + setItems((prev) => prev.filter((i) => i !== item)) + return { previousItems } + }, + onSuccess: (deletedItem) => { + setMessage(`deleted: ${deletedItem}`) + }, + onError: (_error, _item, context) => { + setMessage('rollback') + if (context?.previousItems) { + setItems(context.previousItems) + } + }, + retry: false, + }) + + return ( +
+ {items.map((item) => ( + + ))} +
items: {items.join(', ')}
+
message: {message || 'none'}
+
+ ) + } + + const rendered = renderWithClient(queryClient, ) + + expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() + expect(rendered.getByText('message: none')).toBeInTheDocument() + + fireEvent.click(rendered.getByRole('button', { name: /delete item2/i })) + + // optimistic update: item2 removed immediately + expect(rendered.getByText('items: item1, item3')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + + // rollback: item2 restored after error, onSuccess not called + expect(rendered.getByText('items: item1, item2, item3')).toBeInTheDocument() + expect(rendered.getByText('message: rollback')).toBeInTheDocument() + }) }) diff --git a/packages/svelte-query/tests/HydrationBoundary/BaseExample.svelte b/packages/svelte-query/tests/HydrationBoundary/Base.svelte similarity index 100% rename from packages/svelte-query/tests/HydrationBoundary/BaseExample.svelte rename to packages/svelte-query/tests/HydrationBoundary/Base.svelte diff --git a/packages/svelte-query/tests/HydrationBoundary/HydrationBoundary.svelte.test.ts b/packages/svelte-query/tests/HydrationBoundary/HydrationBoundary.svelte.test.ts index 4fa2e6cc044..42e3a371c34 100644 --- a/packages/svelte-query/tests/HydrationBoundary/HydrationBoundary.svelte.test.ts +++ b/packages/svelte-query/tests/HydrationBoundary/HydrationBoundary.svelte.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { render } from '@testing-library/svelte' import { QueryClient, dehydrate } from '@tanstack/query-core' import { sleep } from '@tanstack/query-test-utils' -import BaseExample from './BaseExample.svelte' +import Base from './Base.svelte' describe('HydrationBoundary', () => { let queryClient: QueryClient @@ -30,7 +30,7 @@ describe('HydrationBoundary', () => { it('should hydrate queries to the cache on context', async () => { const dehydratedState = JSON.parse(stringifiedState) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, dehydratedState, diff --git a/packages/svelte-query/tests/QueryClientProvider/BaseExample.svelte b/packages/svelte-query/tests/QueryClientProvider/Base.svelte similarity index 82% rename from packages/svelte-query/tests/QueryClientProvider/BaseExample.svelte rename to packages/svelte-query/tests/QueryClientProvider/Base.svelte index f7f8fe61c46..108cdbc54fa 100644 --- a/packages/svelte-query/tests/QueryClientProvider/BaseExample.svelte +++ b/packages/svelte-query/tests/QueryClientProvider/Base.svelte @@ -1,6 +1,6 @@ - + diff --git a/packages/svelte-query/tests/QueryClientProvider/QueryChild.svelte b/packages/svelte-query/tests/QueryClientProvider/Child.svelte similarity index 100% rename from packages/svelte-query/tests/QueryClientProvider/QueryChild.svelte rename to packages/svelte-query/tests/QueryClientProvider/Child.svelte diff --git a/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts b/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts index 480c63bb32d..58cad3f7cfd 100644 --- a/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts +++ b/packages/svelte-query/tests/QueryClientProvider/QueryClientProvider.svelte.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { render } from '@testing-library/svelte' import { QueryClient } from '@tanstack/query-core' -import BaseExample from './BaseExample.svelte' +import Base from './Base.svelte' describe('QueryClientProvider', () => { let queryClient: QueryClient @@ -19,7 +19,7 @@ describe('QueryClientProvider', () => { test('should set a specific cache for all queries to use', async () => { const queryCache = queryClient.getQueryCache() - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, }, diff --git a/packages/svelte-query/tests/context/BaseExample.svelte b/packages/svelte-query/tests/context/Base.svelte similarity index 100% rename from packages/svelte-query/tests/context/BaseExample.svelte rename to packages/svelte-query/tests/context/Base.svelte diff --git a/packages/svelte-query/tests/context/context.svelte.test.ts b/packages/svelte-query/tests/context/context.svelte.test.ts index 602989d3f33..483ac3d26f5 100644 --- a/packages/svelte-query/tests/context/context.svelte.test.ts +++ b/packages/svelte-query/tests/context/context.svelte.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from 'vitest' import { render } from '@testing-library/svelte' import { getIsRestoringContext } from '../../src/index.js' -import BaseExample from './BaseExample.svelte' +import Base from './Base.svelte' describe('getQueryClientContext', () => { test('should throw when called without a client in context', () => { - expect(() => render(BaseExample)).toThrow( + expect(() => render(Base)).toThrow( 'No QueryClient was found in Svelte context. Did you forget to wrap your component with QueryClientProvider?', ) }) diff --git a/packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte b/packages/svelte-query/tests/createInfiniteQuery/Base.svelte similarity index 100% rename from packages/svelte-query/tests/createInfiniteQuery/BaseExample.svelte rename to packages/svelte-query/tests/createInfiniteQuery/Base.svelte diff --git a/packages/svelte-query/tests/createInfiniteQuery/ChangeClientExample.svelte b/packages/svelte-query/tests/createInfiniteQuery/ChangeClient.svelte similarity index 100% rename from packages/svelte-query/tests/createInfiniteQuery/ChangeClientExample.svelte rename to packages/svelte-query/tests/createInfiniteQuery/ChangeClient.svelte diff --git a/packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte b/packages/svelte-query/tests/createInfiniteQuery/Select.svelte similarity index 100% rename from packages/svelte-query/tests/createInfiniteQuery/SelectExample.svelte rename to packages/svelte-query/tests/createInfiniteQuery/Select.svelte diff --git a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts index 34bc64577de..b51b5a65616 100644 --- a/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts +++ b/packages/svelte-query/tests/createInfiniteQuery/createInfiniteQuery.svelte.test.ts @@ -2,9 +2,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { fireEvent, render } from '@testing-library/svelte' import { QueryClient } from '@tanstack/query-core' import { ref } from '../utils.svelte.js' -import BaseExample from './BaseExample.svelte' -import SelectExample from './SelectExample.svelte' -import ChangeClientExample from './ChangeClientExample.svelte' +import Base from './Base.svelte' +import Select from './Select.svelte' +import ChangeClient from './ChangeClient.svelte' import type { QueryObserverResult } from '@tanstack/query-core' describe('createInfiniteQuery', () => { @@ -23,7 +23,7 @@ describe('createInfiniteQuery', () => { it('should return the correct states for a successful query', async () => { let states = ref>([]) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, states, @@ -113,7 +113,7 @@ describe('createInfiniteQuery', () => { it('should be able to select a part of the data', async () => { let states = ref>([]) - const rendered = render(SelectExample, { + const rendered = render(Select, { props: { queryClient, states, @@ -137,7 +137,7 @@ describe('createInfiniteQuery', () => { }) it('should be able to set new pages with the query client', async () => { - const rendered = render(ChangeClientExample, { + const rendered = render(ChangeClient, { props: { queryClient, }, diff --git a/packages/svelte-query/tests/createMutation/FailureExample.svelte b/packages/svelte-query/tests/createMutation/Failure.svelte similarity index 100% rename from packages/svelte-query/tests/createMutation/FailureExample.svelte rename to packages/svelte-query/tests/createMutation/Failure.svelte diff --git a/packages/svelte-query/tests/createMutation/ResetExample.svelte b/packages/svelte-query/tests/createMutation/Reset.svelte similarity index 100% rename from packages/svelte-query/tests/createMutation/ResetExample.svelte rename to packages/svelte-query/tests/createMutation/Reset.svelte diff --git a/packages/svelte-query/tests/createMutation/SuccessExample.svelte b/packages/svelte-query/tests/createMutation/Success.svelte similarity index 100% rename from packages/svelte-query/tests/createMutation/SuccessExample.svelte rename to packages/svelte-query/tests/createMutation/Success.svelte diff --git a/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts b/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts index 140cb91c81d..9367bed000f 100644 --- a/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts +++ b/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts @@ -5,9 +5,9 @@ import { QueryClient } from '@tanstack/query-core' import { sleep } from '@tanstack/query-test-utils' import { createMutation } from '../../src/index.js' import { withEffectRoot } from '../utils.svelte.js' -import ResetExample from './ResetExample.svelte' -import SuccessExample from './SuccessExample.svelte' -import FailureExample from './FailureExample.svelte' +import Reset from './Reset.svelte' +import Success from './Success.svelte' +import Failure from './Failure.svelte' describe('createMutation', () => { let queryClient: QueryClient @@ -23,7 +23,7 @@ describe('createMutation', () => { }) test('should be able to reset `error`', async () => { - const rendered = render(ResetExample, { + const rendered = render(Reset, { props: { queryClient }, }) @@ -42,7 +42,7 @@ describe('createMutation', () => { const onSuccessMock = vi.fn() const onSettledMock = vi.fn() - const rendered = render(SuccessExample, { + const rendered = render(Success, { props: { queryClient, onSuccessMock, @@ -80,7 +80,7 @@ describe('createMutation', () => { mutationFn.mockImplementation((value) => sleep(10).then(() => value)) - const rendered = render(FailureExample, { + const rendered = render(Failure, { props: { queryClient, mutationFn, diff --git a/packages/svelte-query/tests/createQueries/IsRestoringExample.svelte b/packages/svelte-query/tests/createQueries/IsRestoring.svelte similarity index 100% rename from packages/svelte-query/tests/createQueries/IsRestoringExample.svelte rename to packages/svelte-query/tests/createQueries/IsRestoring.svelte diff --git a/packages/svelte-query/tests/createQueries/createQueries.svelte.test.ts b/packages/svelte-query/tests/createQueries/createQueries.svelte.test.ts index a4fd69d6d10..26af47d1c87 100644 --- a/packages/svelte-query/tests/createQueries/createQueries.svelte.test.ts +++ b/packages/svelte-query/tests/createQueries/createQueries.svelte.test.ts @@ -3,7 +3,7 @@ import { render } from '@testing-library/svelte' import { queryKey, sleep } from '@tanstack/query-test-utils' import { QueryClient, createQueries } from '../../src/index.js' import { promiseWithResolvers, withEffectRoot } from '../utils.svelte.js' -import IsRestoringExample from './IsRestoringExample.svelte' +import IsRestoring from './IsRestoring.svelte' import type { CreateQueryResult } from '../../src/index.js' describe('createQueries', () => { @@ -273,7 +273,7 @@ describe('createQueries', () => { const queryFn1 = vi.fn(() => sleep(10).then(() => 'data1')) const queryFn2 = vi.fn(() => sleep(10).then(() => 'data2')) - const rendered = render(IsRestoringExample, { + const rendered = render(IsRestoring, { props: { queryClient, queryFn1, queryFn2 }, }) @@ -304,7 +304,7 @@ describe('createQueries', () => { const queryFn1 = vi.fn(() => sleep(10).then(() => 'data1')) const queryFn2 = vi.fn(() => sleep(20).then(() => 'data2')) - const rendered = render(IsRestoringExample, { + const rendered = render(IsRestoring, { props: { queryClient, queryFn1, queryFn2 }, }) diff --git a/packages/svelte-query/tests/createQuery/IsRestoringExample.svelte b/packages/svelte-query/tests/createQuery/IsRestoring.svelte similarity index 100% rename from packages/svelte-query/tests/createQuery/IsRestoringExample.svelte rename to packages/svelte-query/tests/createQuery/IsRestoring.svelte diff --git a/packages/svelte-query/tests/createQuery/createQuery.svelte.test.ts b/packages/svelte-query/tests/createQuery/createQuery.svelte.test.ts index 15b038a8654..1b002dec930 100644 --- a/packages/svelte-query/tests/createQuery/createQuery.svelte.test.ts +++ b/packages/svelte-query/tests/createQuery/createQuery.svelte.test.ts @@ -12,7 +12,7 @@ import { import { queryKey, sleep } from '@tanstack/query-test-utils' import { QueryClient, createQuery, keepPreviousData } from '../../src/index.js' import { promiseWithResolvers, withEffectRoot } from '../utils.svelte.js' -import IsRestoringExample from './IsRestoringExample.svelte' +import IsRestoring from './IsRestoring.svelte' import type { CreateQueryResult, QueryCache } from '../../src/index.js' describe('createQuery', () => { @@ -1923,7 +1923,7 @@ describe('createQuery', () => { it('should not fetch for the duration of the restoring period when isRestoring is true', async () => { const queryFn = vi.fn(() => sleep(10).then(() => 'data')) - const rendered = render(IsRestoringExample, { + const rendered = render(IsRestoring, { props: { queryClient, queryFn }, }) diff --git a/packages/svelte-query/tests/mutationOptions/BaseExample.svelte b/packages/svelte-query/tests/mutationOptions/Base.svelte similarity index 100% rename from packages/svelte-query/tests/mutationOptions/BaseExample.svelte rename to packages/svelte-query/tests/mutationOptions/Base.svelte diff --git a/packages/svelte-query/tests/mutationOptions/MultiExample.svelte b/packages/svelte-query/tests/mutationOptions/Multi.svelte similarity index 100% rename from packages/svelte-query/tests/mutationOptions/MultiExample.svelte rename to packages/svelte-query/tests/mutationOptions/Multi.svelte diff --git a/packages/svelte-query/tests/mutationOptions/mutationOptions.svelte.test.ts b/packages/svelte-query/tests/mutationOptions/mutationOptions.svelte.test.ts index 36c0da27de4..be577f503ce 100644 --- a/packages/svelte-query/tests/mutationOptions/mutationOptions.svelte.test.ts +++ b/packages/svelte-query/tests/mutationOptions/mutationOptions.svelte.test.ts @@ -3,8 +3,8 @@ import { fireEvent, render } from '@testing-library/svelte' import { QueryClient } from '@tanstack/query-core' import { queryKey, sleep } from '@tanstack/query-test-utils' import { mutationOptions } from '../../src/index.js' -import BaseExample from './BaseExample.svelte' -import MultiExample from './MultiExample.svelte' +import Base from './Base.svelte' +import Multi from './Multi.svelte' describe('mutationOptions', () => { let queryClient: QueryClient @@ -43,7 +43,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(50).then(() => 'data'), }) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, mutationOpts: () => mutationOpts }, }) @@ -61,7 +61,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(50).then(() => 'data'), }) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, mutationOpts: () => mutationOpts }, }) @@ -84,7 +84,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(50).then(() => 'data2'), }) - const rendered = render(MultiExample, { + const rendered = render(Multi, { props: { queryClient, mutationOpts1: () => mutationOpts1, @@ -112,7 +112,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(50).then(() => 'data2'), }) - const rendered = render(MultiExample, { + const rendered = render(Multi, { props: { queryClient, mutationOpts1: () => mutationOpts1, @@ -138,7 +138,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(500).then(() => 'data'), }) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, mutationOpts: () => mutationOpts, @@ -160,7 +160,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(500).then(() => 'data'), }) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, mutationOpts: () => mutationOpts }, }) @@ -183,7 +183,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(500).then(() => 'data2'), }) - const rendered = render(MultiExample, { + const rendered = render(Multi, { props: { queryClient, mutationOpts1: () => mutationOpts1, @@ -211,7 +211,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(500).then(() => 'data2'), }) - const rendered = render(MultiExample, { + const rendered = render(Multi, { props: { queryClient, mutationOpts1: () => mutationOpts1, @@ -237,7 +237,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(10).then(() => 'data'), }) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, mutationOpts: () => mutationOpts, @@ -262,7 +262,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(10).then(() => 'data'), }) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, mutationOpts: () => mutationOpts, @@ -289,7 +289,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(10).then(() => 'data2'), }) - const rendered = render(MultiExample, { + const rendered = render(Multi, { props: { queryClient, mutationOpts1: () => mutationOpts1, @@ -320,7 +320,7 @@ describe('mutationOptions', () => { mutationFn: () => sleep(10).then(() => 'data2'), }) - const rendered = render(MultiExample, { + const rendered = render(Multi, { props: { queryClient, mutationOpts1: () => mutationOpts1, diff --git a/packages/svelte-query/tests/useIsFetching/BaseExample.svelte b/packages/svelte-query/tests/useIsFetching/Base.svelte similarity index 100% rename from packages/svelte-query/tests/useIsFetching/BaseExample.svelte rename to packages/svelte-query/tests/useIsFetching/Base.svelte diff --git a/packages/svelte-query/tests/useIsFetching/useIsFetching.svelte.test.ts b/packages/svelte-query/tests/useIsFetching/useIsFetching.svelte.test.ts index 01969664333..5357b004048 100644 --- a/packages/svelte-query/tests/useIsFetching/useIsFetching.svelte.test.ts +++ b/packages/svelte-query/tests/useIsFetching/useIsFetching.svelte.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { fireEvent, render } from '@testing-library/svelte' import { QueryClient } from '@tanstack/query-core' -import BaseExample from './BaseExample.svelte' +import Base from './Base.svelte' describe('useIsFetching', () => { let queryClient: QueryClient @@ -17,7 +17,7 @@ describe('useIsFetching', () => { }) test('should update as queries start and stop fetching', async () => { - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient }, }) diff --git a/packages/svelte-query/tests/useIsMutating/BaseExample.svelte b/packages/svelte-query/tests/useIsMutating/Base.svelte similarity index 100% rename from packages/svelte-query/tests/useIsMutating/BaseExample.svelte rename to packages/svelte-query/tests/useIsMutating/Base.svelte diff --git a/packages/svelte-query/tests/useIsMutating/useIsMutating.svelte.test.ts b/packages/svelte-query/tests/useIsMutating/useIsMutating.svelte.test.ts index cec67dc0579..87c7f397ec2 100644 --- a/packages/svelte-query/tests/useIsMutating/useIsMutating.svelte.test.ts +++ b/packages/svelte-query/tests/useIsMutating/useIsMutating.svelte.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { fireEvent, render } from '@testing-library/svelte' import { QueryClient } from '@tanstack/query-core' -import BaseExample from './BaseExample.svelte' +import Base from './Base.svelte' describe('useIsMutating', () => { let queryClient: QueryClient @@ -17,7 +17,7 @@ describe('useIsMutating', () => { }) test('should update as queries start and stop mutating', async () => { - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient }, }) diff --git a/packages/svelte-query/tests/useMutationState/BaseExample.svelte b/packages/svelte-query/tests/useMutationState/Base.svelte similarity index 100% rename from packages/svelte-query/tests/useMutationState/BaseExample.svelte rename to packages/svelte-query/tests/useMutationState/Base.svelte diff --git a/packages/svelte-query/tests/useMutationState/SelectExample.svelte b/packages/svelte-query/tests/useMutationState/Select.svelte similarity index 100% rename from packages/svelte-query/tests/useMutationState/SelectExample.svelte rename to packages/svelte-query/tests/useMutationState/Select.svelte diff --git a/packages/svelte-query/tests/useMutationState/useMutationState.svelte.test.ts b/packages/svelte-query/tests/useMutationState/useMutationState.svelte.test.ts index d32f28eff5d..f5e2c464116 100644 --- a/packages/svelte-query/tests/useMutationState/useMutationState.svelte.test.ts +++ b/packages/svelte-query/tests/useMutationState/useMutationState.svelte.test.ts @@ -2,8 +2,8 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { fireEvent, render } from '@testing-library/svelte' import { QueryClient } from '@tanstack/query-core' import { queryKey, sleep } from '@tanstack/query-test-utils' -import BaseExample from './BaseExample.svelte' -import SelectExample from './SelectExample.svelte' +import Base from './Base.svelte' +import Select from './Select.svelte' import type { Mutation } from '@tanstack/query-core' describe('useMutationState', () => { @@ -29,7 +29,7 @@ describe('useMutationState', () => { sleep(20).then(() => Promise.reject(new Error('error'))), ) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, successMutationOpts: () => ({ @@ -64,7 +64,7 @@ describe('useMutationState', () => { sleep(20).then(() => Promise.reject(new Error('error'))), ) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, successMutationOpts: () => ({ @@ -95,7 +95,7 @@ describe('useMutationState', () => { test('should return selected value when using select option', async () => { const mutationKey = queryKey() - const rendered = render(SelectExample, { + const rendered = render(Select, { props: { queryClient, mutationOpts: () => ({ @@ -129,7 +129,7 @@ describe('useMutationState', () => { sleep(20).then(() => Promise.reject(new Error('error'))), ) - const rendered = render(BaseExample, { + const rendered = render(Base, { props: { queryClient, successMutationOpts: () => ({